Package ganeti :: Package storage :: Module drbd
[hide private]
[frames] | no frames]

Source Code for Module ganeti.storage.drbd

   1  # 
   2  # 
   3   
   4  # Copyright (C) 2006, 2007, 2010, 2011, 2012, 2013 Google Inc. 
   5  # All rights reserved. 
   6  # 
   7  # Redistribution and use in source and binary forms, with or without 
   8  # modification, are permitted provided that the following conditions are 
   9  # met: 
  10  # 
  11  # 1. Redistributions of source code must retain the above copyright notice, 
  12  # this list of conditions and the following disclaimer. 
  13  # 
  14  # 2. Redistributions in binary form must reproduce the above copyright 
  15  # notice, this list of conditions and the following disclaimer in the 
  16  # documentation and/or other materials provided with the distribution. 
  17  # 
  18  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 
  19  # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 
  20  # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
  21  # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 
  22  # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
  23  # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
  24  # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
  25  # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
  26  # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
  27  # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
  28  # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
  29   
  30   
  31  """DRBD block device related functionality""" 
  32   
  33  import errno 
  34  import logging 
  35  import time 
  36   
  37  from ganeti import constants 
  38  from ganeti import utils 
  39  from ganeti import errors 
  40  from ganeti import netutils 
  41  from ganeti import objects 
  42  from ganeti.storage import base 
  43  from ganeti.storage.drbd_info import DRBD8Info 
  44  from ganeti.storage import drbd_info 
  45  from ganeti.storage import drbd_cmdgen 
  46   
  47   
  48  # Size of reads in _CanReadDevice 
  49   
  50  _DEVICE_READ_SIZE = 128 * 1024 
51 52 53 -class DRBD8(object):
54 """Various methods to deals with the DRBD system as a whole. 55 56 This class provides a set of methods to deal with the DRBD installation on 57 the node or with uninitialized devices as opposed to a DRBD device. 58 59 """ 60 _USERMODE_HELPER_FILE = "/sys/module/drbd/parameters/usermode_helper" 61 62 _MAX_MINORS = 255 63 64 @staticmethod
66 """Returns DRBD usermode_helper currently set. 67 68 @type filename: string 69 @param filename: the filename to read the usermode helper from 70 @rtype: string 71 @return: the currently configured DRBD usermode helper 72 73 """ 74 try: 75 helper = utils.ReadFile(filename).splitlines()[0] 76 except EnvironmentError, err: 77 if err.errno == errno.ENOENT: 78 base.ThrowError("The file %s cannot be opened, check if the module" 79 " is loaded (%s)", filename, str(err)) 80 else: 81 base.ThrowError("Can't read DRBD helper file %s: %s", 82 filename, str(err)) 83 if not helper: 84 base.ThrowError("Can't read any data from %s", filename) 85 return helper
86 87 @staticmethod
88 - def GetProcInfo():
89 """Reads and parses information from /proc/drbd. 90 91 @rtype: DRBD8Info 92 @return: a L{DRBD8Info} instance containing the current /proc/drbd info 93 94 """ 95 return DRBD8Info.CreateFromFile()
96 97 @staticmethod
98 - def GetUsedDevs():
99 """Compute the list of used DRBD minors. 100 101 @rtype: list of ints 102 103 """ 104 info = DRBD8.GetProcInfo() 105 return filter(lambda m: not info.GetMinorStatus(m).is_unconfigured, 106 info.GetMinors())
107 108 @staticmethod
109 - def FindUnusedMinor():
110 """Find an unused DRBD device. 111 112 This is specific to 8.x as the minors are allocated dynamically, 113 so non-existing numbers up to a max minor count are actually free. 114 115 @rtype: int 116 117 """ 118 highest = None 119 info = DRBD8.GetProcInfo() 120 for minor in info.GetMinors(): 121 status = info.GetMinorStatus(minor) 122 if not status.is_in_use: 123 return minor 124 highest = max(highest, minor) 125 126 if highest is None: # there are no minors in use at all 127 return 0 128 if highest >= DRBD8._MAX_MINORS: 129 logging.error("Error: no free drbd minors!") 130 raise errors.BlockDeviceError("Can't find a free DRBD minor") 131 132 return highest + 1
133 134 @staticmethod
135 - def GetCmdGenerator(info):
136 """Creates a suitable L{BaseDRBDCmdGenerator} based on the given info. 137 138 @type info: DRBD8Info 139 @rtype: BaseDRBDCmdGenerator 140 141 """ 142 version = info.GetVersion() 143 if version["k_minor"] <= 3: 144 return drbd_cmdgen.DRBD83CmdGenerator(version) 145 else: 146 return drbd_cmdgen.DRBD84CmdGenerator(version)
147 148 @staticmethod
149 - def ShutdownAll(minor):
150 """Deactivate the device. 151 152 This will, of course, fail if the device is in use. 153 154 @type minor: int 155 @param minor: the minor to shut down 156 157 """ 158 info = DRBD8.GetProcInfo() 159 cmd_gen = DRBD8.GetCmdGenerator(info) 160 161 cmd = cmd_gen.GenDownCmd(minor) 162 result = utils.RunCmd(cmd) 163 if result.failed: 164 base.ThrowError("drbd%d: can't shutdown drbd device: %s", 165 minor, result.output)
166
167 168 -class DRBD8Dev(base.BlockDev):
169 """DRBD v8.x block device. 170 171 This implements the local host part of the DRBD device, i.e. it 172 doesn't do anything to the supposed peer. If you need a fully 173 connected DRBD pair, you need to use this class on both hosts. 174 175 The unique_id for the drbd device is a (pnode_uuid, snode_uuid, 176 port, pnode_minor, lnode_minor, secret) tuple, and it must have 177 two children: the data device and the meta_device. The meta 178 device is checked for valid size and is zeroed on create. 179 180 """ 181 _DRBD_MAJOR = 147 182 183 # timeout constants 184 _NET_RECONFIG_TIMEOUT = 60 185
186 - def __init__(self, unique_id, children, size, params, dyn_params, *args):
187 if children and children.count(None) > 0: 188 children = [] 189 if len(children) not in (0, 2): 190 raise ValueError("Invalid configuration data %s" % str(children)) 191 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 6: 192 raise ValueError("Invalid configuration data %s" % str(unique_id)) 193 if constants.DDP_LOCAL_IP not in dyn_params or \ 194 constants.DDP_REMOTE_IP not in dyn_params or \ 195 constants.DDP_LOCAL_MINOR not in dyn_params or \ 196 constants.DDP_REMOTE_MINOR not in dyn_params: 197 raise ValueError("Invalid dynamic parameters %s" % str(dyn_params)) 198 199 self._lhost = dyn_params[constants.DDP_LOCAL_IP] 200 self._lport = unique_id[2] 201 self._rhost = dyn_params[constants.DDP_REMOTE_IP] 202 self._rport = unique_id[2] 203 self._aminor = dyn_params[constants.DDP_LOCAL_MINOR] 204 self._secret = unique_id[5] 205 206 if children: 207 if not _CanReadDevice(children[1].dev_path): 208 logging.info("drbd%s: Ignoring unreadable meta device", self._aminor) 209 children = [] 210 super(DRBD8Dev, self).__init__(unique_id, children, size, params, 211 dyn_params, *args) 212 self.major = self._DRBD_MAJOR 213 214 info = DRBD8.GetProcInfo() 215 version = info.GetVersion() 216 if version["k_major"] != 8: 217 base.ThrowError("Mismatch in DRBD kernel version and requested ganeti" 218 " usage: kernel is %s.%s, ganeti wants 8.x", 219 version["k_major"], version["k_minor"]) 220 221 if version["k_minor"] <= 3: 222 self._show_info_cls = drbd_info.DRBD83ShowInfo 223 else: 224 self._show_info_cls = drbd_info.DRBD84ShowInfo 225 226 self._cmd_gen = DRBD8.GetCmdGenerator(info) 227 228 if (self._lhost is not None and self._lhost == self._rhost and 229 self._lport == self._rport): 230 raise ValueError("Invalid configuration data, same local/remote %s, %s" % 231 (unique_id, dyn_params)) 232 self.Attach()
233 234 @staticmethod
235 - def _DevPath(minor):
236 """Return the path to a drbd device for a given minor. 237 238 @type minor: int 239 @rtype: string 240 241 """ 242 return "/dev/drbd%d" % minor
243
244 - def _SetFromMinor(self, minor):
245 """Set our parameters based on the given minor. 246 247 This sets our minor variable and our dev_path. 248 249 @type minor: int 250 251 """ 252 if minor is None: 253 self.minor = self.dev_path = None 254 self.attached = False 255 else: 256 self.minor = minor 257 self.dev_path = self._DevPath(minor) 258 self.attached = True
259 260 @staticmethod
261 - def _CheckMetaSize(meta_device):
262 """Check if the given meta device looks like a valid one. 263 264 This currently only checks the size, which must be around 265 128MiB. 266 267 @type meta_device: string 268 @param meta_device: the path to the device to check 269 270 """ 271 result = utils.RunCmd(["blockdev", "--getsize", meta_device]) 272 if result.failed: 273 base.ThrowError("Failed to get device size: %s - %s", 274 result.fail_reason, result.output) 275 try: 276 sectors = int(result.stdout) 277 except (TypeError, ValueError): 278 base.ThrowError("Invalid output from blockdev: '%s'", result.stdout) 279 num_bytes = sectors * 512 280 if num_bytes < 128 * 1024 * 1024: # less than 128MiB 281 base.ThrowError("Meta device too small (%.2fMib)", 282 (num_bytes / 1024 / 1024)) 283 # the maximum *valid* size of the meta device when living on top 284 # of LVM is hard to compute: it depends on the number of stripes 285 # and the PE size; e.g. a 2-stripe, 64MB PE will result in a 128MB 286 # (normal size), but an eight-stripe 128MB PE will result in a 1GB 287 # size meta device; as such, we restrict it to 1GB (a little bit 288 # too generous, but making assumptions about PE size is hard) 289 if num_bytes > 1024 * 1024 * 1024: 290 base.ThrowError("Meta device too big (%.2fMiB)", 291 (num_bytes / 1024 / 1024))
292
293 - def _GetShowData(self, minor):
294 """Return the `drbdsetup show` data. 295 296 @type minor: int 297 @param minor: the minor to collect show output for 298 @rtype: string 299 300 """ 301 result = utils.RunCmd(self._cmd_gen.GenShowCmd(minor)) 302 if result.failed: 303 logging.error("Can't display the drbd config: %s - %s", 304 result.fail_reason, result.output) 305 return None 306 return result.stdout
307
308 - def _GetShowInfo(self, minor):
309 """Return parsed information from `drbdsetup show`. 310 311 @type minor: int 312 @param minor: the minor to return information for 313 @rtype: dict as described in L{drbd_info.BaseShowInfo.GetDevInfo} 314 315 """ 316 return self._show_info_cls.GetDevInfo(self._GetShowData(minor))
317
318 - def _MatchesLocal(self, info):
319 """Test if our local config matches with an existing device. 320 321 The parameter should be as returned from `_GetShowInfo()`. This 322 method tests if our local backing device is the same as the one in 323 the info parameter, in effect testing if we look like the given 324 device. 325 326 @type info: dict as described in L{drbd_info.BaseShowInfo.GetDevInfo} 327 @rtype: boolean 328 329 """ 330 if self._children: 331 backend, meta = self._children 332 else: 333 backend = meta = None 334 335 if backend is not None: 336 retval = ("local_dev" in info and info["local_dev"] == backend.dev_path) 337 else: 338 retval = ("local_dev" not in info) 339 340 if meta is not None: 341 retval = retval and ("meta_dev" in info and 342 info["meta_dev"] == meta.dev_path) 343 if "meta_index" in info: 344 retval = retval and info["meta_index"] == 0 345 else: 346 retval = retval and ("meta_dev" not in info and 347 "meta_index" not in info) 348 return retval
349
350 - def _MatchesNet(self, info):
351 """Test if our network config matches with an existing device. 352 353 The parameter should be as returned from `_GetShowInfo()`. This 354 method tests if our network configuration is the same as the one 355 in the info parameter, in effect testing if we look like the given 356 device. 357 358 @type info: dict as described in L{drbd_info.BaseShowInfo.GetDevInfo} 359 @rtype: boolean 360 361 """ 362 if (((self._lhost is None and not ("local_addr" in info)) and 363 (self._rhost is None and not ("remote_addr" in info)))): 364 return True 365 366 if self._lhost is None: 367 return False 368 369 if not ("local_addr" in info and 370 "remote_addr" in info): 371 return False 372 373 retval = (info["local_addr"] == (self._lhost, self._lport)) 374 retval = (retval and 375 info["remote_addr"] == (self._rhost, self._rport)) 376 return retval
377
378 - def _AssembleLocal(self, minor, backend, meta, size):
379 """Configure the local part of a DRBD device. 380 381 @type minor: int 382 @param minor: the minor to assemble locally 383 @type backend: string 384 @param backend: path to the data device to use 385 @type meta: string 386 @param meta: path to the meta device to use 387 @type size: int 388 @param size: size in MiB 389 390 """ 391 cmds = self._cmd_gen.GenLocalInitCmds(minor, backend, meta, 392 size, self.params) 393 394 for cmd in cmds: 395 result = utils.RunCmd(cmd) 396 if result.failed: 397 base.ThrowError("drbd%d: can't attach local disk: %s", 398 minor, result.output)
399
400 - def _AssembleNet(self, minor, net_info, dual_pri=False, hmac=None, 401 secret=None):
402 """Configure the network part of the device. 403 404 @type minor: int 405 @param minor: the minor to assemble the network for 406 @type net_info: (string, int, string, int) 407 @param net_info: tuple containing the local address, local port, remote 408 address and remote port 409 @type dual_pri: boolean 410 @param dual_pri: whether two primaries should be allowed or not 411 @type hmac: string 412 @param hmac: the HMAC algorithm to use 413 @type secret: string 414 @param secret: the shared secret to use 415 416 """ 417 lhost, lport, rhost, rport = net_info 418 if None in net_info: 419 # we don't want network connection and actually want to make 420 # sure its shutdown 421 self._ShutdownNet(minor) 422 return 423 424 if dual_pri: 425 protocol = constants.DRBD_MIGRATION_NET_PROTOCOL 426 else: 427 protocol = self.params[constants.LDP_PROTOCOL] 428 429 # Workaround for a race condition. When DRBD is doing its dance to 430 # establish a connection with its peer, it also sends the 431 # synchronization speed over the wire. In some cases setting the 432 # sync speed only after setting up both sides can race with DRBD 433 # connecting, hence we set it here before telling DRBD anything 434 # about its peer. 435 sync_errors = self._SetMinorSyncParams(minor, self.params) 436 if sync_errors: 437 base.ThrowError("drbd%d: can't set the synchronization parameters: %s" % 438 (minor, utils.CommaJoin(sync_errors))) 439 440 family = self._GetNetFamily(minor, lhost, rhost) 441 442 cmd = self._cmd_gen.GenNetInitCmd(minor, family, lhost, lport, 443 rhost, rport, protocol, 444 dual_pri, hmac, secret, self.params) 445 446 result = utils.RunCmd(cmd) 447 if result.failed: 448 base.ThrowError("drbd%d: can't setup network: %s - %s", 449 minor, result.fail_reason, result.output) 450 451 def _CheckNetworkConfig(): 452 info = self._GetShowInfo(minor) 453 if not "local_addr" in info or not "remote_addr" in info: 454 raise utils.RetryAgain() 455 456 if (info["local_addr"] != (lhost, lport) or 457 info["remote_addr"] != (rhost, rport)): 458 raise utils.RetryAgain()
459 460 try: 461 utils.Retry(_CheckNetworkConfig, 1.0, 10.0) 462 except utils.RetryTimeout: 463 base.ThrowError("drbd%d: timeout while configuring network", minor) 464 465 # Once the assembly is over, try to set the synchronization parameters 466 try: 467 # The minor may not have been set yet, requiring us to set it at least 468 # temporarily 469 old_minor = self.minor 470 self._SetFromMinor(minor) 471 sync_errors = self.SetSyncParams(self.params) 472 if sync_errors: 473 base.ThrowError("drbd%d: can't set the synchronization parameters: %s" % 474 (self.minor, utils.CommaJoin(sync_errors))) 475 finally: 476 # Undo the change, regardless of whether it will have to be done again 477 # soon 478 self._SetFromMinor(old_minor)
479 480 @staticmethod
481 - def _GetNetFamily(minor, lhost, rhost):
482 if netutils.IP6Address.IsValid(lhost): 483 if not netutils.IP6Address.IsValid(rhost): 484 base.ThrowError("drbd%d: can't connect ip %s to ip %s" % 485 (minor, lhost, rhost)) 486 return "ipv6" 487 elif netutils.IP4Address.IsValid(lhost): 488 if not netutils.IP4Address.IsValid(rhost): 489 base.ThrowError("drbd%d: can't connect ip %s to ip %s" % 490 (minor, lhost, rhost)) 491 return "ipv4" 492 else: 493 base.ThrowError("drbd%d: Invalid ip %s" % (minor, lhost))
494
495 - def AddChildren(self, devices):
496 """Add a disk to the DRBD device. 497 498 @type devices: list of L{BlockDev} 499 @param devices: a list of exactly two L{BlockDev} objects; the first 500 denotes the data device, the second the meta device for this DRBD device 501 502 """ 503 if self.minor is None: 504 base.ThrowError("drbd%d: can't attach to dbrd8 during AddChildren", 505 self._aminor) 506 if len(devices) != 2: 507 base.ThrowError("drbd%d: need two devices for AddChildren", self.minor) 508 info = self._GetShowInfo(self.minor) 509 if "local_dev" in info: 510 base.ThrowError("drbd%d: already attached to a local disk", self.minor) 511 backend, meta = devices 512 if backend.dev_path is None or meta.dev_path is None: 513 base.ThrowError("drbd%d: children not ready during AddChildren", 514 self.minor) 515 backend.Open() 516 meta.Open() 517 self._CheckMetaSize(meta.dev_path) 518 self._InitMeta(DRBD8.FindUnusedMinor(), meta.dev_path) 519 520 self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path, self.size) 521 self._children = devices
522
523 - def RemoveChildren(self, devices):
524 """Detach the drbd device from local storage. 525 526 @type devices: list of L{BlockDev} 527 @param devices: a list of exactly two L{BlockDev} objects; the first 528 denotes the data device, the second the meta device for this DRBD device 529 530 """ 531 if self.minor is None: 532 base.ThrowError("drbd%d: can't attach to drbd8 during RemoveChildren", 533 self._aminor) 534 # early return if we don't actually have backing storage 535 info = self._GetShowInfo(self.minor) 536 if "local_dev" not in info: 537 return 538 if len(self._children) != 2: 539 base.ThrowError("drbd%d: we don't have two children: %s", self.minor, 540 self._children) 541 if self._children.count(None) == 2: # we don't actually have children :) 542 logging.warning("drbd%d: requested detach while detached", self.minor) 543 return 544 if len(devices) != 2: 545 base.ThrowError("drbd%d: we need two children in RemoveChildren", 546 self.minor) 547 for child, dev in zip(self._children, devices): 548 if dev != child.dev_path: 549 base.ThrowError("drbd%d: mismatch in local storage (%s != %s) in" 550 " RemoveChildren", self.minor, dev, child.dev_path) 551 552 self._ShutdownLocal(self.minor) 553 self._children = []
554
555 - def _SetMinorSyncParams(self, minor, params):
556 """Set the parameters of the DRBD syncer. 557 558 This is the low-level implementation. 559 560 @type minor: int 561 @param minor: the drbd minor whose settings we change 562 @type params: dict 563 @param params: LD level disk parameters related to the synchronization 564 @rtype: list 565 @return: a list of error messages 566 567 """ 568 cmd = self._cmd_gen.GenSyncParamsCmd(minor, params) 569 result = utils.RunCmd(cmd) 570 if result.failed: 571 msg = ("Can't change syncer rate: %s - %s" % 572 (result.fail_reason, result.output)) 573 logging.error(msg) 574 return [msg] 575 576 return []
577
578 - def SetSyncParams(self, params):
579 """Set the synchronization parameters of the DRBD syncer. 580 581 See L{BlockDev.SetSyncParams} for parameter description. 582 583 """ 584 if self.minor is None: 585 err = "Not attached during SetSyncParams" 586 logging.info(err) 587 return [err] 588 589 children_result = super(DRBD8Dev, self).SetSyncParams(params) 590 children_result.extend(self._SetMinorSyncParams(self.minor, params)) 591 return children_result
592
593 - def PauseResumeSync(self, pause):
594 """Pauses or resumes the sync of a DRBD device. 595 596 See L{BlockDev.PauseResumeSync} for parameter description. 597 598 """ 599 if self.minor is None: 600 logging.info("Not attached during PauseSync") 601 return False 602 603 children_result = super(DRBD8Dev, self).PauseResumeSync(pause) 604 605 if pause: 606 cmd = self._cmd_gen.GenPauseSyncCmd(self.minor) 607 else: 608 cmd = self._cmd_gen.GenResumeSyncCmd(self.minor) 609 610 result = utils.RunCmd(cmd) 611 if result.failed: 612 logging.error("Can't %s: %s - %s", cmd, 613 result.fail_reason, result.output) 614 return not result.failed and children_result
615
616 - def GetProcStatus(self):
617 """Return the current status data from /proc/drbd for this device. 618 619 @rtype: DRBD8Status 620 621 """ 622 if self.minor is None: 623 base.ThrowError("drbd%d: GetStats() called while not attached", 624 self._aminor) 625 info = DRBD8.GetProcInfo() 626 if not info.HasMinorStatus(self.minor): 627 base.ThrowError("drbd%d: can't find myself in /proc", self.minor) 628 return info.GetMinorStatus(self.minor)
629
630 - def GetSyncStatus(self):
631 """Returns the sync status of the device. 632 633 If sync_percent is None, it means all is ok 634 If estimated_time is None, it means we can't estimate 635 the time needed, otherwise it's the time left in seconds. 636 637 We set the is_degraded parameter to True on two conditions: 638 network not connected or local disk missing. 639 640 We compute the ldisk parameter based on whether we have a local 641 disk or not. 642 643 @rtype: objects.BlockDevStatus 644 645 """ 646 if self.minor is None and not self.Attach(): 647 base.ThrowError("drbd%d: can't Attach() in GetSyncStatus", self._aminor) 648 649 stats = self.GetProcStatus() 650 is_degraded = not stats.is_connected or not stats.is_disk_uptodate 651 652 if stats.is_disk_uptodate: 653 ldisk_status = constants.LDS_OKAY 654 elif stats.is_diskless: 655 ldisk_status = constants.LDS_FAULTY 656 else: 657 ldisk_status = constants.LDS_UNKNOWN 658 659 return objects.BlockDevStatus(dev_path=self.dev_path, 660 major=self.major, 661 minor=self.minor, 662 sync_percent=stats.sync_percent, 663 estimated_time=stats.est_time, 664 is_degraded=is_degraded, 665 ldisk_status=ldisk_status)
666
667 - def Open(self, force=False):
668 """Make the local state primary. 669 670 If the 'force' parameter is given, DRBD is instructed to switch the device 671 into primary mode. Since this is a potentially dangerous operation, the 672 force flag should be only given after creation, when it actually is 673 mandatory. 674 675 """ 676 if self.minor is None and not self.Attach(): 677 logging.error("DRBD cannot attach to a device during open") 678 return False 679 680 cmd = self._cmd_gen.GenPrimaryCmd(self.minor, force) 681 682 result = utils.RunCmd(cmd) 683 if result.failed: 684 base.ThrowError("drbd%d: can't make drbd device primary: %s", self.minor, 685 result.output)
686
687 - def Close(self):
688 """Make the local state secondary. 689 690 This will, of course, fail if the device is in use. 691 692 """ 693 if self.minor is None and not self.Attach(): 694 base.ThrowError("drbd%d: can't Attach() in Close()", self._aminor) 695 cmd = self._cmd_gen.GenSecondaryCmd(self.minor) 696 result = utils.RunCmd(cmd) 697 if result.failed: 698 base.ThrowError("drbd%d: can't switch drbd device to secondary: %s", 699 self.minor, result.output)
700
701 - def DisconnectNet(self):
702 """Removes network configuration. 703 704 This method shutdowns the network side of the device. 705 706 The method will wait up to a hardcoded timeout for the device to 707 go into standalone after the 'disconnect' command before 708 re-configuring it, as sometimes it takes a while for the 709 disconnect to actually propagate and thus we might issue a 'net' 710 command while the device is still connected. If the device will 711 still be attached to the network and we time out, we raise an 712 exception. 713 714 """ 715 if self.minor is None: 716 base.ThrowError("drbd%d: disk not attached in re-attach net", 717 self._aminor) 718 719 if None in (self._lhost, self._lport, self._rhost, self._rport): 720 base.ThrowError("drbd%d: DRBD disk missing network info in" 721 " DisconnectNet()", self.minor) 722 723 class _DisconnectStatus(object): 724 def __init__(self, ever_disconnected): 725 self.ever_disconnected = ever_disconnected
726 727 dstatus = _DisconnectStatus(base.IgnoreError(self._ShutdownNet, self.minor)) 728 729 def _WaitForDisconnect(): 730 if self.GetProcStatus().is_standalone: 731 return 732 733 # retry the disconnect, it seems possible that due to a well-time 734 # disconnect on the peer, my disconnect command might be ignored and 735 # forgotten 736 dstatus.ever_disconnected = \ 737 base.IgnoreError(self._ShutdownNet, self.minor) or \ 738 dstatus.ever_disconnected 739 740 raise utils.RetryAgain() 741 742 # Keep start time 743 start_time = time.time() 744 745 try: 746 # Start delay at 100 milliseconds and grow up to 2 seconds 747 utils.Retry(_WaitForDisconnect, (0.1, 1.5, 2.0), 748 self._NET_RECONFIG_TIMEOUT) 749 except utils.RetryTimeout: 750 if dstatus.ever_disconnected: 751 msg = ("drbd%d: device did not react to the" 752 " 'disconnect' command in a timely manner") 753 else: 754 msg = "drbd%d: can't shutdown network, even after multiple retries" 755 756 base.ThrowError(msg, self.minor) 757 758 reconfig_time = time.time() - start_time 759 if reconfig_time > (self._NET_RECONFIG_TIMEOUT * 0.25): 760 logging.info("drbd%d: DisconnectNet: detach took %.3f seconds", 761 self.minor, reconfig_time) 762
763 - def AttachNet(self, multimaster):
764 """Reconnects the network. 765 766 This method connects the network side of the device with a 767 specified multi-master flag. The device needs to be 'Standalone' 768 but have valid network configuration data. 769 770 @type multimaster: boolean 771 @param multimaster: init the network in dual-primary mode 772 773 """ 774 if self.minor is None: 775 base.ThrowError("drbd%d: device not attached in AttachNet", self._aminor) 776 777 if None in (self._lhost, self._lport, self._rhost, self._rport): 778 base.ThrowError("drbd%d: missing network info in AttachNet()", self.minor) 779 780 status = self.GetProcStatus() 781 782 if not status.is_standalone: 783 base.ThrowError("drbd%d: device is not standalone in AttachNet", 784 self.minor) 785 786 self._AssembleNet(self.minor, 787 (self._lhost, self._lport, self._rhost, self._rport), 788 dual_pri=multimaster, hmac=constants.DRBD_HMAC_ALG, 789 secret=self._secret)
790
791 - def Attach(self):
792 """Check if our minor is configured. 793 794 This doesn't do any device configurations - it only checks if the 795 minor is in a state different from Unconfigured. 796 797 Note that this function will not change the state of the system in 798 any way (except in case of side-effects caused by reading from 799 /proc). 800 801 """ 802 used_devs = DRBD8.GetUsedDevs() 803 if self._aminor in used_devs: 804 minor = self._aminor 805 else: 806 minor = None 807 808 self._SetFromMinor(minor) 809 return minor is not None
810
811 - def Assemble(self):
812 """Assemble the drbd. 813 814 Method: 815 - if we have a configured device, we try to ensure that it matches 816 our config 817 - if not, we create it from zero 818 - anyway, set the device parameters 819 820 """ 821 super(DRBD8Dev, self).Assemble() 822 823 self.Attach() 824 if self.minor is None: 825 # local device completely unconfigured 826 self._FastAssemble() 827 else: 828 # we have to recheck the local and network status and try to fix 829 # the device 830 self._SlowAssemble()
831
832 - def _SlowAssemble(self):
833 """Assembles the DRBD device from a (partially) configured device. 834 835 In case of partially attached (local device matches but no network 836 setup), we perform the network attach. If successful, we re-test 837 the attach if can return success. 838 839 """ 840 # TODO: Rewrite to not use a for loop just because there is 'break' 841 # pylint: disable=W0631 842 net_data = (self._lhost, self._lport, self._rhost, self._rport) 843 for minor in (self._aminor,): 844 info = self._GetShowInfo(minor) 845 match_l = self._MatchesLocal(info) 846 match_r = self._MatchesNet(info) 847 848 if match_l and match_r: 849 # everything matches 850 break 851 852 if match_l and not match_r and "local_addr" not in info: 853 # disk matches, but not attached to network, attach and recheck 854 self._AssembleNet(minor, net_data, hmac=constants.DRBD_HMAC_ALG, 855 secret=self._secret) 856 if self._MatchesNet(self._GetShowInfo(minor)): 857 break 858 else: 859 base.ThrowError("drbd%d: network attach successful, but 'drbdsetup" 860 " show' disagrees", minor) 861 862 if match_r and "local_dev" not in info: 863 # no local disk, but network attached and it matches 864 self._AssembleLocal(minor, self._children[0].dev_path, 865 self._children[1].dev_path, self.size) 866 if self._MatchesLocal(self._GetShowInfo(minor)): 867 break 868 else: 869 base.ThrowError("drbd%d: disk attach successful, but 'drbdsetup" 870 " show' disagrees", minor) 871 872 # this case must be considered only if we actually have local 873 # storage, i.e. not in diskless mode, because all diskless 874 # devices are equal from the point of view of local 875 # configuration 876 if (match_l and "local_dev" in info and 877 not match_r and "local_addr" in info): 878 # strange case - the device network part points to somewhere 879 # else, even though its local storage is ours; as we own the 880 # drbd space, we try to disconnect from the remote peer and 881 # reconnect to our correct one 882 try: 883 self._ShutdownNet(minor) 884 except errors.BlockDeviceError, err: 885 base.ThrowError("drbd%d: device has correct local storage, wrong" 886 " remote peer and is unable to disconnect in order" 887 " to attach to the correct peer: %s", minor, str(err)) 888 # note: _AssembleNet also handles the case when we don't want 889 # local storage (i.e. one or more of the _[lr](host|port) is 890 # None) 891 self._AssembleNet(minor, net_data, hmac=constants.DRBD_HMAC_ALG, 892 secret=self._secret) 893 if self._MatchesNet(self._GetShowInfo(minor)): 894 break 895 else: 896 base.ThrowError("drbd%d: network attach successful, but 'drbdsetup" 897 " show' disagrees", minor) 898 899 else: 900 minor = None 901 902 self._SetFromMinor(minor) 903 if minor is None: 904 base.ThrowError("drbd%d: cannot activate, unknown or unhandled reason", 905 self._aminor)
906
907 - def _FastAssemble(self):
908 """Assemble the drbd device from zero. 909 910 This is run when in Assemble we detect our minor is unused. 911 912 """ 913 minor = self._aminor 914 if self._children and self._children[0] and self._children[1]: 915 self._AssembleLocal(minor, self._children[0].dev_path, 916 self._children[1].dev_path, self.size) 917 if self._lhost and self._lport and self._rhost and self._rport: 918 self._AssembleNet(minor, 919 (self._lhost, self._lport, self._rhost, self._rport), 920 hmac=constants.DRBD_HMAC_ALG, secret=self._secret) 921 self._SetFromMinor(minor)
922
923 - def _ShutdownLocal(self, minor):
924 """Detach from the local device. 925 926 I/Os will continue to be served from the remote device. If we 927 don't have a remote device, this operation will fail. 928 929 @type minor: int 930 @param minor: the device to detach from the local device 931 932 """ 933 cmd = self._cmd_gen.GenDetachCmd(minor) 934 result = utils.RunCmd(cmd) 935 if result.failed: 936 base.ThrowError("drbd%d: can't detach local disk: %s", 937 minor, result.output)
938
939 - def _ShutdownNet(self, minor):
940 """Disconnect from the remote peer. 941 942 This fails if we don't have a local device. 943 944 @type minor: boolean 945 @param minor: the device to disconnect from the remote peer 946 947 """ 948 family = self._GetNetFamily(minor, self._lhost, self._rhost) 949 cmd = self._cmd_gen.GenDisconnectCmd(minor, family, 950 self._lhost, self._lport, 951 self._rhost, self._rport) 952 result = utils.RunCmd(cmd) 953 if result.failed: 954 base.ThrowError("drbd%d: can't shutdown network: %s", 955 minor, result.output)
956
957 - def Shutdown(self):
958 """Shutdown the DRBD device. 959 960 """ 961 if self.minor is None and not self.Attach(): 962 logging.info("drbd%d: not attached during Shutdown()", self._aminor) 963 return 964 965 try: 966 DRBD8.ShutdownAll(self.minor) 967 finally: 968 self.minor = None 969 self.dev_path = None
970
971 - def Remove(self):
972 """Stub remove for DRBD devices. 973 974 """ 975 self.Shutdown()
976
977 - def Rename(self, new_id):
978 """Rename a device. 979 980 This is not supported for drbd devices. 981 982 """ 983 raise errors.ProgrammerError("Can't rename a drbd device")
984
985 - def Grow(self, amount, dryrun, backingstore, excl_stor):
986 """Resize the DRBD device and its backing storage. 987 988 See L{BlockDev.Grow} for parameter description. 989 990 """ 991 if self.minor is None: 992 base.ThrowError("drbd%d: Grow called while not attached", self._aminor) 993 if len(self._children) != 2 or None in self._children: 994 base.ThrowError("drbd%d: cannot grow diskless device", self.minor) 995 self._children[0].Grow(amount, dryrun, backingstore, excl_stor) 996 if dryrun or backingstore: 997 # DRBD does not support dry-run mode and is not backing storage, 998 # so we'll return here 999 return 1000 cmd = self._cmd_gen.GenResizeCmd(self.minor, self.size + amount) 1001 result = utils.RunCmd(cmd) 1002 if result.failed: 1003 base.ThrowError("drbd%d: resize failed: %s", self.minor, result.output)
1004 1005 @classmethod
1006 - def _InitMeta(cls, minor, dev_path):
1007 """Initialize a meta device. 1008 1009 This will not work if the given minor is in use. 1010 1011 @type minor: int 1012 @param minor: the DRBD minor whose (future) meta device should be 1013 initialized 1014 @type dev_path: string 1015 @param dev_path: path to the meta device to initialize 1016 1017 """ 1018 # Zero the metadata first, in order to make sure drbdmeta doesn't 1019 # try to auto-detect existing filesystems or similar (see 1020 # http://code.google.com/p/ganeti/issues/detail?id=182); we only 1021 # care about the first 128MB of data in the device, even though it 1022 # can be bigger 1023 result = utils.RunCmd([constants.DD_CMD, 1024 "if=/dev/zero", "of=%s" % dev_path, 1025 "bs=1048576", "count=128", "oflag=direct"]) 1026 if result.failed: 1027 base.ThrowError("Can't wipe the meta device: %s", result.output) 1028 1029 info = DRBD8.GetProcInfo() 1030 cmd_gen = DRBD8.GetCmdGenerator(info) 1031 cmd = cmd_gen.GenInitMetaCmd(minor, dev_path) 1032 1033 result = utils.RunCmd(cmd) 1034 if result.failed: 1035 base.ThrowError("Can't initialize meta device: %s", result.output)
1036 1037 @classmethod
1038 - def Create(cls, unique_id, children, size, spindles, params, excl_stor, 1039 dyn_params, *_):
1040 """Create a new DRBD8 device. 1041 1042 Since DRBD devices are not created per se, just assembled, this 1043 function only initializes the metadata. 1044 1045 """ 1046 if len(children) != 2: 1047 raise errors.ProgrammerError("Invalid setup for the drbd device") 1048 if excl_stor: 1049 raise errors.ProgrammerError("DRBD device requested with" 1050 " exclusive_storage") 1051 if constants.DDP_LOCAL_MINOR not in dyn_params: 1052 raise errors.ProgrammerError("Invalid dynamic params for drbd device %s" 1053 % dyn_params) 1054 # check that the minor is unused 1055 aminor = dyn_params[constants.DDP_LOCAL_MINOR] 1056 1057 info = DRBD8.GetProcInfo() 1058 if info.HasMinorStatus(aminor): 1059 status = info.GetMinorStatus(aminor) 1060 in_use = status.is_in_use 1061 else: 1062 in_use = False 1063 if in_use: 1064 base.ThrowError("drbd%d: minor is already in use at Create() time", 1065 aminor) 1066 meta = children[1] 1067 meta.Assemble() 1068 if not meta.Attach(): 1069 base.ThrowError("drbd%d: can't attach to meta device '%s'", 1070 aminor, meta) 1071 cls._CheckMetaSize(meta.dev_path) 1072 cls._InitMeta(aminor, meta.dev_path) 1073 return cls(unique_id, children, size, params, dyn_params)
1074
1075 1076 -def _CanReadDevice(path):
1077 """Check if we can read from the given device. 1078 1079 This tries to read the first 128k of the device. 1080 1081 @type path: string 1082 1083 """ 1084 try: 1085 utils.ReadFile(path, size=_DEVICE_READ_SIZE) 1086 return True 1087 except EnvironmentError: 1088 logging.warning("Can't read from device %s", path, exc_info=True) 1089 return False
1090