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