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