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 else: 659 ldisk_status = constants.LDS_UNKNOWN 660 661 return objects.BlockDevStatus(dev_path=self.dev_path, 662 major=self.major, 663 minor=self.minor, 664 sync_percent=stats.sync_percent, 665 estimated_time=stats.est_time, 666 is_degraded=is_degraded, 667 ldisk_status=ldisk_status)
668
669 - def Open(self, force=False):
670 """Make the local state primary. 671 672 If the 'force' parameter is given, DRBD is instructed to switch the device 673 into primary mode. Since this is a potentially dangerous operation, the 674 force flag should be only given after creation, when it actually is 675 mandatory. 676 677 """ 678 if self.minor is None and not self.Attach(): 679 logging.error("DRBD cannot attach to a device during open") 680 return False 681 682 cmd = self._cmd_gen.GenPrimaryCmd(self.minor, force) 683 684 result = utils.RunCmd(cmd) 685 if result.failed: 686 base.ThrowError("drbd%d: can't make drbd device primary: %s", self.minor, 687 result.output)
688
689 - def Close(self):
690 """Make the local state secondary. 691 692 This will, of course, fail if the device is in use. 693 694 """ 695 if self.minor is None and not self.Attach(): 696 base.ThrowError("drbd%d: can't Attach() in Close()", self._aminor) 697 cmd = self._cmd_gen.GenSecondaryCmd(self.minor) 698 result = utils.RunCmd(cmd) 699 if result.failed: 700 base.ThrowError("drbd%d: can't switch drbd device to secondary: %s", 701 self.minor, result.output)
702
703 - def DisconnectNet(self):
704 """Removes network configuration. 705 706 This method shutdowns the network side of the device. 707 708 The method will wait up to a hardcoded timeout for the device to 709 go into standalone after the 'disconnect' command before 710 re-configuring it, as sometimes it takes a while for the 711 disconnect to actually propagate and thus we might issue a 'net' 712 command while the device is still connected. If the device will 713 still be attached to the network and we time out, we raise an 714 exception. 715 716 """ 717 if self.minor is None: 718 base.ThrowError("drbd%d: disk not attached in re-attach net", 719 self._aminor) 720 721 if None in (self._lhost, self._lport, self._rhost, self._rport): 722 base.ThrowError("drbd%d: DRBD disk missing network info in" 723 " DisconnectNet()", self.minor) 724 725 class _DisconnectStatus(object): 726 def __init__(self, ever_disconnected): 727 self.ever_disconnected = ever_disconnected
728 729 dstatus = _DisconnectStatus(base.IgnoreError(self._ShutdownNet, self.minor)) 730 731 def _WaitForDisconnect(): 732 if self.GetProcStatus().is_standalone: 733 return 734 735 # retry the disconnect, it seems possible that due to a well-time 736 # disconnect on the peer, my disconnect command might be ignored and 737 # forgotten 738 dstatus.ever_disconnected = \ 739 base.IgnoreError(self._ShutdownNet, self.minor) or \ 740 dstatus.ever_disconnected 741 742 raise utils.RetryAgain() 743 744 # Keep start time 745 start_time = time.time() 746 747 try: 748 # Start delay at 100 milliseconds and grow up to 2 seconds 749 utils.Retry(_WaitForDisconnect, (0.1, 1.5, 2.0), 750 self._NET_RECONFIG_TIMEOUT) 751 except utils.RetryTimeout: 752 if dstatus.ever_disconnected: 753 msg = ("drbd%d: device did not react to the" 754 " 'disconnect' command in a timely manner") 755 else: 756 msg = "drbd%d: can't shutdown network, even after multiple retries" 757 758 base.ThrowError(msg, self.minor) 759 760 reconfig_time = time.time() - start_time 761 if reconfig_time > (self._NET_RECONFIG_TIMEOUT * 0.25): 762 logging.info("drbd%d: DisconnectNet: detach took %.3f seconds", 763 self.minor, reconfig_time) 764
765 - def AttachNet(self, multimaster):
766 """Reconnects the network. 767 768 This method connects the network side of the device with a 769 specified multi-master flag. The device needs to be 'Standalone' 770 but have valid network configuration data. 771 772 @type multimaster: boolean 773 @param multimaster: init the network in dual-primary mode 774 775 """ 776 if self.minor is None: 777 base.ThrowError("drbd%d: device not attached in AttachNet", self._aminor) 778 779 if None in (self._lhost, self._lport, self._rhost, self._rport): 780 base.ThrowError("drbd%d: missing network info in AttachNet()", self.minor) 781 782 status = self.GetProcStatus() 783 784 if not status.is_standalone: 785 base.ThrowError("drbd%d: device is not standalone in AttachNet", 786 self.minor) 787 788 self._AssembleNet(self.minor, 789 (self._lhost, self._lport, self._rhost, self._rport), 790 dual_pri=multimaster, hmac=constants.DRBD_HMAC_ALG, 791 secret=self._secret)
792
793 - def Attach(self):
794 """Check if our minor is configured. 795 796 This doesn't do any device configurations - it only checks if the 797 minor is in a state different from Unconfigured. 798 799 Note that this function will not change the state of the system in 800 any way (except in case of side-effects caused by reading from 801 /proc). 802 803 """ 804 used_devs = DRBD8.GetUsedDevs() 805 if self._aminor in used_devs: 806 minor = self._aminor 807 else: 808 minor = None 809 810 self._SetFromMinor(minor) 811 return minor is not None
812
813 - def Assemble(self):
814 """Assemble the drbd. 815 816 Method: 817 - if we have a configured device, we try to ensure that it matches 818 our config 819 - if not, we create it from zero 820 - anyway, set the device parameters 821 822 """ 823 super(DRBD8Dev, self).Assemble() 824 825 self.Attach() 826 if self.minor is None: 827 # local device completely unconfigured 828 self._FastAssemble() 829 else: 830 # we have to recheck the local and network status and try to fix 831 # the device 832 self._SlowAssemble()
833
834 - def _SlowAssemble(self):
835 """Assembles the DRBD device from a (partially) configured device. 836 837 In case of partially attached (local device matches but no network 838 setup), we perform the network attach. If successful, we re-test 839 the attach if can return success. 840 841 """ 842 # TODO: Rewrite to not use a for loop just because there is 'break' 843 # pylint: disable=W0631 844 net_data = (self._lhost, self._lport, self._rhost, self._rport) 845 for minor in (self._aminor,): 846 info = self._GetShowInfo(minor) 847 match_l = self._MatchesLocal(info) 848 match_r = self._MatchesNet(info) 849 850 if match_l and match_r: 851 # everything matches 852 break 853 854 if match_l and not match_r and "local_addr" not in info: 855 # disk matches, but not attached to network, attach and recheck 856 self._AssembleNet(minor, net_data, hmac=constants.DRBD_HMAC_ALG, 857 secret=self._secret) 858 if self._MatchesNet(self._GetShowInfo(minor)): 859 break 860 else: 861 base.ThrowError("drbd%d: network attach successful, but 'drbdsetup" 862 " show' disagrees", minor) 863 864 if match_r and "local_dev" not in info: 865 # no local disk, but network attached and it matches 866 self._AssembleLocal(minor, self._children[0].dev_path, 867 self._children[1].dev_path, self.size) 868 if self._MatchesLocal(self._GetShowInfo(minor)): 869 break 870 else: 871 base.ThrowError("drbd%d: disk attach successful, but 'drbdsetup" 872 " show' disagrees", minor) 873 874 # this case must be considered only if we actually have local 875 # storage, i.e. not in diskless mode, because all diskless 876 # devices are equal from the point of view of local 877 # configuration 878 if (match_l and "local_dev" in info and 879 not match_r and "local_addr" in info): 880 # strange case - the device network part points to somewhere 881 # else, even though its local storage is ours; as we own the 882 # drbd space, we try to disconnect from the remote peer and 883 # reconnect to our correct one 884 try: 885 self._ShutdownNet(minor) 886 except errors.BlockDeviceError, err: 887 base.ThrowError("drbd%d: device has correct local storage, wrong" 888 " remote peer and is unable to disconnect in order" 889 " to attach to the correct peer: %s", minor, str(err)) 890 # note: _AssembleNet also handles the case when we don't want 891 # local storage (i.e. one or more of the _[lr](host|port) is 892 # None) 893 self._AssembleNet(minor, net_data, hmac=constants.DRBD_HMAC_ALG, 894 secret=self._secret) 895 if self._MatchesNet(self._GetShowInfo(minor)): 896 break 897 else: 898 base.ThrowError("drbd%d: network attach successful, but 'drbdsetup" 899 " show' disagrees", minor) 900 901 else: 902 minor = None 903 904 self._SetFromMinor(minor) 905 if minor is None: 906 base.ThrowError("drbd%d: cannot activate, unknown or unhandled reason", 907 self._aminor)
908
909 - def _FastAssemble(self):
910 """Assemble the drbd device from zero. 911 912 This is run when in Assemble we detect our minor is unused. 913 914 """ 915 minor = self._aminor 916 if self._children and self._children[0] and self._children[1]: 917 self._AssembleLocal(minor, self._children[0].dev_path, 918 self._children[1].dev_path, self.size) 919 if self._lhost and self._lport and self._rhost and self._rport: 920 self._AssembleNet(minor, 921 (self._lhost, self._lport, self._rhost, self._rport), 922 hmac=constants.DRBD_HMAC_ALG, secret=self._secret) 923 self._SetFromMinor(minor)
924
925 - def _ShutdownLocal(self, minor):
926 """Detach from the local device. 927 928 I/Os will continue to be served from the remote device. If we 929 don't have a remote device, this operation will fail. 930 931 @type minor: int 932 @param minor: the device to detach from the local device 933 934 """ 935 cmd = self._cmd_gen.GenDetachCmd(minor) 936 result = utils.RunCmd(cmd) 937 if result.failed: 938 base.ThrowError("drbd%d: can't detach local disk: %s", 939 minor, result.output)
940
941 - def _ShutdownNet(self, minor):
942 """Disconnect from the remote peer. 943 944 This fails if we don't have a local device. 945 946 @type minor: boolean 947 @param minor: the device to disconnect from the remote peer 948 949 """ 950 family = self._GetNetFamily(minor, self._lhost, self._rhost) 951 cmd = self._cmd_gen.GenDisconnectCmd(minor, family, 952 self._lhost, self._lport, 953 self._rhost, self._rport) 954 result = utils.RunCmd(cmd) 955 if result.failed: 956 base.ThrowError("drbd%d: can't shutdown network: %s", 957 minor, result.output)
958
959 - def Shutdown(self):
960 """Shutdown the DRBD device. 961 962 """ 963 if self.minor is None and not self.Attach(): 964 logging.info("drbd%d: not attached during Shutdown()", self._aminor) 965 return 966 967 try: 968 DRBD8.ShutdownAll(self.minor) 969 finally: 970 self.minor = None 971 self.dev_path = None
972
973 - def Remove(self):
974 """Stub remove for DRBD devices. 975 976 """ 977 self.Shutdown()
978
979 - def Rename(self, new_id):
980 """Rename a device. 981 982 This is not supported for drbd devices. 983 984 """ 985 raise errors.ProgrammerError("Can't rename a drbd device")
986
987 - def Grow(self, amount, dryrun, backingstore, excl_stor):
988 """Resize the DRBD device and its backing storage. 989 990 See L{BlockDev.Grow} for parameter description. 991 992 """ 993 if self.minor is None: 994 base.ThrowError("drbd%d: Grow called while not attached", self._aminor) 995 if len(self._children) != 2 or None in self._children: 996 base.ThrowError("drbd%d: cannot grow diskless device", self.minor) 997 self._children[0].Grow(amount, dryrun, backingstore, excl_stor) 998 if dryrun or backingstore: 999 # DRBD does not support dry-run mode and is not backing storage, 1000 # so we'll return here 1001 return 1002 cmd = self._cmd_gen.GenResizeCmd(self.minor, self.size + amount) 1003 result = utils.RunCmd(cmd) 1004 if result.failed: 1005 base.ThrowError("drbd%d: resize failed: %s", self.minor, result.output)
1006 1007 @classmethod
1008 - def _InitMeta(cls, minor, dev_path):
1009 """Initialize a meta device. 1010 1011 This will not work if the given minor is in use. 1012 1013 @type minor: int 1014 @param minor: the DRBD minor whose (future) meta device should be 1015 initialized 1016 @type dev_path: string 1017 @param dev_path: path to the meta device to initialize 1018 1019 """ 1020 # Zero the metadata first, in order to make sure drbdmeta doesn't 1021 # try to auto-detect existing filesystems or similar (see 1022 # http://code.google.com/p/ganeti/issues/detail?id=182); we only 1023 # care about the first 128MB of data in the device, even though it 1024 # can be bigger 1025 result = utils.RunCmd([constants.DD_CMD, 1026 "if=/dev/zero", "of=%s" % dev_path, 1027 "bs=1048576", "count=128", "oflag=direct"]) 1028 if result.failed: 1029 base.ThrowError("Can't wipe the meta device: %s", result.output) 1030 1031 info = DRBD8.GetProcInfo() 1032 cmd_gen = DRBD8.GetCmdGenerator(info) 1033 cmd = cmd_gen.GenInitMetaCmd(minor, dev_path) 1034 1035 result = utils.RunCmd(cmd) 1036 if result.failed: 1037 base.ThrowError("Can't initialize meta device: %s", result.output)
1038 1039 @classmethod
1040 - def Create(cls, unique_id, children, size, spindles, params, excl_stor, 1041 dyn_params, *_):
1042 """Create a new DRBD8 device. 1043 1044 Since DRBD devices are not created per se, just assembled, this 1045 function only initializes the metadata. 1046 1047 """ 1048 if len(children) != 2: 1049 raise errors.ProgrammerError("Invalid setup for the drbd device") 1050 if excl_stor: 1051 raise errors.ProgrammerError("DRBD device requested with" 1052 " exclusive_storage") 1053 if constants.DDP_LOCAL_MINOR not in dyn_params: 1054 raise errors.ProgrammerError("Invalid dynamic params for drbd device %s" 1055 % dyn_params) 1056 # check that the minor is unused 1057 aminor = dyn_params[constants.DDP_LOCAL_MINOR] 1058 1059 info = DRBD8.GetProcInfo() 1060 if info.HasMinorStatus(aminor): 1061 status = info.GetMinorStatus(aminor) 1062 in_use = status.is_in_use 1063 else: 1064 in_use = False 1065 if in_use: 1066 base.ThrowError("drbd%d: minor is already in use at Create() time", 1067 aminor) 1068 meta = children[1] 1069 meta.Assemble() 1070 if not meta.Attach(): 1071 base.ThrowError("drbd%d: can't attach to meta device '%s'", 1072 aminor, meta) 1073 cls._CheckMetaSize(meta.dev_path) 1074 cls._InitMeta(aminor, meta.dev_path) 1075 return cls(unique_id, children, size, params, dyn_params)
1076
1077 1078 -def _CanReadDevice(path):
1079 """Check if we can read from the given device. 1080 1081 This tries to read the first 128k of the device. 1082 1083 @type path: string 1084 1085 """ 1086 try: 1087 utils.ReadFile(path, size=_DEVICE_READ_SIZE) 1088 return True 1089 except EnvironmentError: 1090 logging.warning("Can't read from device %s", path, exc_info=True) 1091 return False
1092