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