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