32 |
32 |
from ganeti import objects
|
33 |
33 |
from ganeti.block import base
|
34 |
34 |
from ganeti.block.drbd_info import DRBD8Info
|
35 |
|
from ganeti.block.drbd_info import DRBD83ShowInfo
|
36 |
|
from ganeti.block.drbd_info import DRBD84ShowInfo
|
|
35 |
from ganeti.block import drbd_info
|
37 |
36 |
from ganeti.block import drbd_cmdgen
|
38 |
37 |
|
39 |
38 |
|
... | ... | |
42 |
41 |
_DEVICE_READ_SIZE = 128 * 1024
|
43 |
42 |
|
44 |
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 |
|
45 |
159 |
class DRBD8Dev(base.BlockDev):
|
46 |
160 |
"""DRBD v8.x block device.
|
47 |
161 |
|
... | ... | |
57 |
171 |
"""
|
58 |
172 |
_DRBD_MAJOR = 147
|
59 |
173 |
|
60 |
|
_USERMODE_HELPER_FILE = "/sys/module/drbd/parameters/usermode_helper"
|
61 |
|
|
62 |
|
_MAX_MINORS = 255
|
63 |
|
|
64 |
174 |
# timeout constants
|
65 |
175 |
_NET_RECONFIG_TIMEOUT = 60
|
66 |
176 |
|
... | ... | |
81 |
191 |
super(DRBD8Dev, self).__init__(unique_id, children, size, params)
|
82 |
192 |
self.major = self._DRBD_MAJOR
|
83 |
193 |
|
84 |
|
drbd_info = DRBD8Info.CreateFromFile()
|
85 |
|
version = drbd_info.GetVersion()
|
|
194 |
info = DRBD8.GetProcInfo()
|
|
195 |
version = info.GetVersion()
|
86 |
196 |
if version["k_major"] != 8:
|
87 |
197 |
base.ThrowError("Mismatch in DRBD kernel version and requested ganeti"
|
88 |
198 |
" usage: kernel is %s.%s, ganeti wants 8.x",
|
89 |
199 |
version["k_major"], version["k_minor"])
|
90 |
200 |
|
91 |
201 |
if version["k_minor"] <= 3:
|
92 |
|
self._show_info_cls = DRBD83ShowInfo
|
|
202 |
self._show_info_cls = drbd_info.DRBD83ShowInfo
|
93 |
203 |
else:
|
94 |
|
self._show_info_cls = DRBD84ShowInfo
|
|
204 |
self._show_info_cls = drbd_info.DRBD84ShowInfo
|
95 |
205 |
|
96 |
|
self._cmd_gen = self._GetCmdGenerator(drbd_info)
|
|
206 |
self._cmd_gen = DRBD8.GetCmdGenerator(info)
|
97 |
207 |
|
98 |
208 |
if (self._lhost is not None and self._lhost == self._rhost and
|
99 |
209 |
self._lport == self._rport):
|
... | ... | |
101 |
211 |
(unique_id,))
|
102 |
212 |
self.Attach()
|
103 |
213 |
|
104 |
|
@classmethod
|
105 |
|
def _GetCmdGenerator(cls, drbd_info):
|
106 |
|
version = drbd_info.GetVersion()
|
107 |
|
if version["k_minor"] <= 3:
|
108 |
|
return drbd_cmdgen.DRBD83CmdGenerator(version)
|
109 |
|
else:
|
110 |
|
return drbd_cmdgen.DRBD84CmdGenerator(version)
|
111 |
|
|
112 |
|
@staticmethod
|
113 |
|
def GetUsermodeHelper(filename=_USERMODE_HELPER_FILE):
|
114 |
|
"""Returns DRBD usermode_helper currently set.
|
115 |
|
|
116 |
|
"""
|
117 |
|
try:
|
118 |
|
helper = utils.ReadFile(filename).splitlines()[0]
|
119 |
|
except EnvironmentError, err:
|
120 |
|
if err.errno == errno.ENOENT:
|
121 |
|
base.ThrowError("The file %s cannot be opened, check if the module"
|
122 |
|
" is loaded (%s)", filename, str(err))
|
123 |
|
else:
|
124 |
|
base.ThrowError("Can't read DRBD helper file %s: %s",
|
125 |
|
filename, str(err))
|
126 |
|
if not helper:
|
127 |
|
base.ThrowError("Can't read any data from %s", filename)
|
128 |
|
return helper
|
129 |
|
|
130 |
214 |
@staticmethod
|
131 |
215 |
def _DevPath(minor):
|
132 |
216 |
"""Return the path to a drbd device for a given minor.
|
133 |
217 |
|
134 |
|
"""
|
135 |
|
return "/dev/drbd%d" % minor
|
136 |
|
|
137 |
|
@classmethod
|
138 |
|
def GetUsedDevs(cls):
|
139 |
|
"""Compute the list of used DRBD devices.
|
|
218 |
@type minor: int
|
|
219 |
@rtype: string
|
140 |
220 |
|
141 |
221 |
"""
|
142 |
|
drbd_info = DRBD8Info.CreateFromFile()
|
143 |
|
return filter(lambda m: not drbd_info.GetMinorStatus(m).is_unconfigured,
|
144 |
|
drbd_info.GetMinors())
|
|
222 |
return "/dev/drbd%d" % minor
|
145 |
223 |
|
146 |
224 |
def _SetFromMinor(self, minor):
|
147 |
225 |
"""Set our parameters based on the given minor.
|
148 |
226 |
|
149 |
227 |
This sets our minor variable and our dev_path.
|
150 |
228 |
|
|
229 |
@type minor: int
|
|
230 |
|
151 |
231 |
"""
|
152 |
232 |
if minor is None:
|
153 |
233 |
self.minor = self.dev_path = None
|
... | ... | |
164 |
244 |
This currently only checks the size, which must be around
|
165 |
245 |
128MiB.
|
166 |
246 |
|
|
247 |
@type meta_device: string
|
|
248 |
@param meta_device: the path to the device to check
|
|
249 |
|
167 |
250 |
"""
|
168 |
251 |
result = utils.RunCmd(["blockdev", "--getsize", meta_device])
|
169 |
252 |
if result.failed:
|
... | ... | |
187 |
270 |
base.ThrowError("Meta device too big (%.2fMiB)",
|
188 |
271 |
(num_bytes / 1024 / 1024))
|
189 |
272 |
|
190 |
|
@classmethod
|
191 |
|
def _InitMeta(cls, minor, dev_path):
|
192 |
|
"""Initialize a meta device.
|
193 |
|
|
194 |
|
This will not work if the given minor is in use.
|
195 |
|
|
196 |
|
"""
|
197 |
|
# Zero the metadata first, in order to make sure drbdmeta doesn't
|
198 |
|
# try to auto-detect existing filesystems or similar (see
|
199 |
|
# http://code.google.com/p/ganeti/issues/detail?id=182); we only
|
200 |
|
# care about the first 128MB of data in the device, even though it
|
201 |
|
# can be bigger
|
202 |
|
result = utils.RunCmd([constants.DD_CMD,
|
203 |
|
"if=/dev/zero", "of=%s" % dev_path,
|
204 |
|
"bs=1048576", "count=128", "oflag=direct"])
|
205 |
|
if result.failed:
|
206 |
|
base.ThrowError("Can't wipe the meta device: %s", result.output)
|
207 |
|
|
208 |
|
drbd_info = DRBD8Info.CreateFromFile()
|
209 |
|
cmd_gen = cls._GetCmdGenerator(drbd_info)
|
210 |
|
cmd = cmd_gen.GenInitMetaCmd(minor, dev_path)
|
211 |
|
|
212 |
|
result = utils.RunCmd(cmd)
|
213 |
|
if result.failed:
|
214 |
|
base.ThrowError("Can't initialize meta device: %s", result.output)
|
215 |
|
|
216 |
|
def _FindUnusedMinor(self):
|
217 |
|
"""Find an unused DRBD device.
|
218 |
|
|
219 |
|
This is specific to 8.x as the minors are allocated dynamically,
|
220 |
|
so non-existing numbers up to a max minor count are actually free.
|
221 |
|
|
222 |
|
"""
|
223 |
|
|
224 |
|
highest = None
|
225 |
|
drbd_info = DRBD8Info.CreateFromFile()
|
226 |
|
for minor in drbd_info.GetMinors():
|
227 |
|
status = drbd_info.GetMinorStatus(minor)
|
228 |
|
if not status.is_in_use:
|
229 |
|
return minor
|
230 |
|
highest = max(highest, minor)
|
231 |
|
|
232 |
|
if highest is None: # there are no minors in use at all
|
233 |
|
return 0
|
234 |
|
if highest >= self._MAX_MINORS:
|
235 |
|
logging.error("Error: no free drbd minors!")
|
236 |
|
raise errors.BlockDeviceError("Can't find a free DRBD minor")
|
237 |
|
|
238 |
|
return highest + 1
|
239 |
|
|
240 |
273 |
def _GetShowData(self, minor):
|
241 |
|
"""Return the `drbdsetup show` data for a 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
|
242 |
279 |
|
243 |
280 |
"""
|
244 |
281 |
result = utils.RunCmd(self._cmd_gen.GenShowCmd(minor))
|
... | ... | |
249 |
286 |
return result.stdout
|
250 |
287 |
|
251 |
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 |
"""
|
252 |
296 |
return self._show_info_cls.GetDevInfo(self._GetShowData(minor))
|
253 |
297 |
|
254 |
298 |
def _MatchesLocal(self, info):
|
255 |
299 |
"""Test if our local config matches with an existing device.
|
256 |
300 |
|
257 |
|
The parameter should be as returned from `_GetDevInfo()`. This
|
|
301 |
The parameter should be as returned from `_GetShowInfo()`. This
|
258 |
302 |
method tests if our local backing device is the same as the one in
|
259 |
303 |
the info parameter, in effect testing if we look like the given
|
260 |
304 |
device.
|
261 |
305 |
|
|
306 |
@type info: dict as described in L{drbd_info.BaseShowInfo.GetDevInfo}
|
|
307 |
@rtype: boolean
|
|
308 |
|
262 |
309 |
"""
|
263 |
310 |
if self._children:
|
264 |
311 |
backend, meta = self._children
|
... | ... | |
283 |
330 |
def _MatchesNet(self, info):
|
284 |
331 |
"""Test if our network config matches with an existing device.
|
285 |
332 |
|
286 |
|
The parameter should be as returned from `_GetDevInfo()`. This
|
|
333 |
The parameter should be as returned from `_GetShowInfo()`. This
|
287 |
334 |
method tests if our network configuration is the same as the one
|
288 |
335 |
in the info parameter, in effect testing if we look like the given
|
289 |
336 |
device.
|
290 |
337 |
|
|
338 |
@type info: dict as described in L{drbd_info.BaseShowInfo.GetDevInfo}
|
|
339 |
@rtype: boolean
|
|
340 |
|
291 |
341 |
"""
|
292 |
342 |
if (((self._lhost is None and not ("local_addr" in info)) and
|
293 |
343 |
(self._rhost is None and not ("remote_addr" in info)))):
|
... | ... | |
308 |
358 |
def _AssembleLocal(self, minor, backend, meta, size):
|
309 |
359 |
"""Configure the local part of a DRBD device.
|
310 |
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 |
|
311 |
370 |
"""
|
312 |
371 |
cmds = self._cmd_gen.GenLocalInitCmds(minor, backend, meta,
|
313 |
372 |
size, self.params)
|
... | ... | |
322 |
381 |
dual_pri=False, hmac=None, secret=None):
|
323 |
382 |
"""Configure the network part of the device.
|
324 |
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 |
|
325 |
398 |
"""
|
326 |
399 |
lhost, lport, rhost, rport = net_info
|
327 |
400 |
if None in net_info:
|
... | ... | |
384 |
457 |
def AddChildren(self, devices):
|
385 |
458 |
"""Add a disk to the DRBD device.
|
386 |
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 |
|
387 |
464 |
"""
|
388 |
465 |
if self.minor is None:
|
389 |
466 |
base.ThrowError("drbd%d: can't attach to dbrd8 during AddChildren",
|
... | ... | |
400 |
477 |
backend.Open()
|
401 |
478 |
meta.Open()
|
402 |
479 |
self._CheckMetaSize(meta.dev_path)
|
403 |
|
self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
|
|
480 |
self._InitMeta(DRBD8.FindUnusedMinor(), meta.dev_path)
|
404 |
481 |
|
405 |
482 |
self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path, self.size)
|
406 |
483 |
self._children = devices
|
... | ... | |
408 |
485 |
def RemoveChildren(self, devices):
|
409 |
486 |
"""Detach the drbd device from local storage.
|
410 |
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 |
|
411 |
492 |
"""
|
412 |
493 |
if self.minor is None:
|
413 |
494 |
base.ThrowError("drbd%d: can't attach to drbd8 during RemoveChildren",
|
... | ... | |
459 |
540 |
def SetSyncParams(self, params):
|
460 |
541 |
"""Set the synchronization parameters of the DRBD syncer.
|
461 |
542 |
|
462 |
|
@type params: dict
|
463 |
|
@param params: LD level disk parameters related to the synchronization
|
464 |
|
@rtype: list
|
465 |
|
@return: a list of error messages, emitted both by the current node and by
|
466 |
|
children. An empty list means no errors
|
|
543 |
See L{BlockDev.SetSyncParams} for parameter description.
|
467 |
544 |
|
468 |
545 |
"""
|
469 |
546 |
if self.minor is None:
|
... | ... | |
478 |
555 |
def PauseResumeSync(self, pause):
|
479 |
556 |
"""Pauses or resumes the sync of a DRBD device.
|
480 |
557 |
|
481 |
|
@param pause: Wether to pause or resume
|
482 |
|
@return: the success of the operation
|
|
558 |
See L{BlockDev.PauseResumeSync} for parameter description.
|
483 |
559 |
|
484 |
560 |
"""
|
485 |
561 |
if self.minor is None:
|
... | ... | |
500 |
576 |
return not result.failed and children_result
|
501 |
577 |
|
502 |
578 |
def GetProcStatus(self):
|
503 |
|
"""Return device data from /proc.
|
|
579 |
"""Return the current status data from /proc/drbd for this device.
|
|
580 |
|
|
581 |
@rtype: DRBD8Status
|
504 |
582 |
|
505 |
583 |
"""
|
506 |
584 |
if self.minor is None:
|
507 |
585 |
base.ThrowError("drbd%d: GetStats() called while not attached",
|
508 |
586 |
self._aminor)
|
509 |
|
drbd_info = DRBD8Info.CreateFromFile()
|
510 |
|
if not drbd_info.HasMinorStatus(self.minor):
|
|
587 |
info = DRBD8.GetProcInfo()
|
|
588 |
if not info.HasMinorStatus(self.minor):
|
511 |
589 |
base.ThrowError("drbd%d: can't find myself in /proc", self.minor)
|
512 |
|
return drbd_info.GetMinorStatus(self.minor)
|
|
590 |
return info.GetMinorStatus(self.minor)
|
513 |
591 |
|
514 |
592 |
def GetSyncStatus(self):
|
515 |
593 |
"""Returns the sync status of the device.
|
516 |
594 |
|
517 |
|
|
518 |
595 |
If sync_percent is None, it means all is ok
|
519 |
596 |
If estimated_time is None, it means we can't estimate
|
520 |
597 |
the time needed, otherwise it's the time left in seconds.
|
521 |
598 |
|
522 |
|
|
523 |
599 |
We set the is_degraded parameter to True on two conditions:
|
524 |
600 |
network not connected or local disk missing.
|
525 |
601 |
|
... | ... | |
553 |
629 |
def Open(self, force=False):
|
554 |
630 |
"""Make the local state primary.
|
555 |
631 |
|
556 |
|
If the 'force' parameter is given, the '-o' option is passed to
|
557 |
|
drbdsetup. Since this is a potentially dangerous operation, the
|
558 |
|
force flag should be only given after creation, when it actually
|
559 |
|
is mandatory.
|
|
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.
|
560 |
636 |
|
561 |
637 |
"""
|
562 |
638 |
if self.minor is None and not self.Attach():
|
... | ... | |
653 |
729 |
specified multi-master flag. The device needs to be 'Standalone'
|
654 |
730 |
but have valid network configuration data.
|
655 |
731 |
|
656 |
|
Args:
|
657 |
|
- multimaster: init the network in dual-primary mode
|
|
732 |
@type multimaster: boolean
|
|
733 |
@param multimaster: init the network in dual-primary mode
|
658 |
734 |
|
659 |
735 |
"""
|
660 |
736 |
if self.minor is None:
|
... | ... | |
685 |
761 |
/proc).
|
686 |
762 |
|
687 |
763 |
"""
|
688 |
|
used_devs = self.GetUsedDevs()
|
|
764 |
used_devs = DRBD8.GetUsedDevs()
|
689 |
765 |
if self._aminor in used_devs:
|
690 |
766 |
minor = self._aminor
|
691 |
767 |
else:
|
... | ... | |
818 |
894 |
I/Os will continue to be served from the remote device. If we
|
819 |
895 |
don't have a remote device, this operation will fail.
|
820 |
896 |
|
|
897 |
@type minor: int
|
|
898 |
@param minor: the device to detach from the local device
|
|
899 |
|
821 |
900 |
"""
|
822 |
901 |
cmd = self._cmd_gen.GenDetachCmd(minor)
|
823 |
902 |
result = utils.RunCmd(cmd)
|
... | ... | |
830 |
909 |
|
831 |
910 |
This fails if we don't have a local device.
|
832 |
911 |
|
|
912 |
@type minor: boolean
|
|
913 |
@param minor: the device to disconnect from the remote peer
|
|
914 |
|
833 |
915 |
"""
|
834 |
916 |
family = self._GetNetFamily(minor, self._lhost, self._rhost)
|
835 |
917 |
cmd = self._cmd_gen.GenDisconnectCmd(minor, family,
|
... | ... | |
840 |
922 |
base.ThrowError("drbd%d: can't shutdown network: %s",
|
841 |
923 |
minor, result.output)
|
842 |
924 |
|
843 |
|
@classmethod
|
844 |
|
def _ShutdownAll(cls, minor):
|
845 |
|
"""Deactivate the device.
|
846 |
|
|
847 |
|
This will, of course, fail if the device is in use.
|
848 |
|
|
849 |
|
"""
|
850 |
|
# FIXME: _ShutdownAll, despite being private, is used in nodemaint.py.
|
851 |
|
# That's why we can't make it an instance method, which in turn requires
|
852 |
|
# us to duplicate code here (from __init__). This should be properly fixed.
|
853 |
|
drbd_info = DRBD8Info.CreateFromFile()
|
854 |
|
cmd_gen = cls._GetCmdGenerator(drbd_info)
|
855 |
|
|
856 |
|
cmd = cmd_gen.GenDownCmd(minor)
|
857 |
|
result = utils.RunCmd(cmd)
|
858 |
|
if result.failed:
|
859 |
|
base.ThrowError("drbd%d: can't shutdown drbd device: %s",
|
860 |
|
minor, result.output)
|
861 |
|
|
862 |
925 |
def Shutdown(self):
|
863 |
926 |
"""Shutdown the DRBD device.
|
864 |
927 |
|
... | ... | |
869 |
932 |
minor = self.minor
|
870 |
933 |
self.minor = None
|
871 |
934 |
self.dev_path = None
|
872 |
|
self._ShutdownAll(minor)
|
|
935 |
DRBD8.ShutdownAll(minor)
|
873 |
936 |
|
874 |
937 |
def Remove(self):
|
875 |
938 |
"""Stub remove for DRBD devices.
|
... | ... | |
885 |
948 |
"""
|
886 |
949 |
raise errors.ProgrammerError("Can't rename a drbd device")
|
887 |
950 |
|
|
951 |
def Grow(self, amount, dryrun, backingstore):
|
|
952 |
"""Resize the DRBD device and its backing storage.
|
|
953 |
|
|
954 |
See L{BlockDev.Grow} for parameter description.
|
|
955 |
|
|
956 |
"""
|
|
957 |
if self.minor is None:
|
|
958 |
base.ThrowError("drbd%d: Grow called while not attached", self._aminor)
|
|
959 |
if len(self._children) != 2 or None in self._children:
|
|
960 |
base.ThrowError("drbd%d: cannot grow diskless device", self.minor)
|
|
961 |
self._children[0].Grow(amount, dryrun, backingstore)
|
|
962 |
if dryrun or backingstore:
|
|
963 |
# DRBD does not support dry-run mode and is not backing storage,
|
|
964 |
# so we'll return here
|
|
965 |
return
|
|
966 |
cmd = self._cmd_gen.GenResizeCmd(self.minor, self.size + amount)
|
|
967 |
result = utils.RunCmd(cmd)
|
|
968 |
if result.failed:
|
|
969 |
base.ThrowError("drbd%d: resize failed: %s", self.minor, result.output)
|
|
970 |
|
|
971 |
@classmethod
|
|
972 |
def _InitMeta(cls, minor, dev_path):
|
|
973 |
"""Initialize a meta device.
|
|
974 |
|
|
975 |
This will not work if the given minor is in use.
|
|
976 |
|
|
977 |
@type minor: int
|
|
978 |
@param minor: the DRBD minor whose (future) meta device should be
|
|
979 |
initialized
|
|
980 |
@type dev_path: string
|
|
981 |
@param dev_path: path to the meta device to initialize
|
|
982 |
|
|
983 |
"""
|
|
984 |
# Zero the metadata first, in order to make sure drbdmeta doesn't
|
|
985 |
# try to auto-detect existing filesystems or similar (see
|
|
986 |
# http://code.google.com/p/ganeti/issues/detail?id=182); we only
|
|
987 |
# care about the first 128MB of data in the device, even though it
|
|
988 |
# can be bigger
|
|
989 |
result = utils.RunCmd([constants.DD_CMD,
|
|
990 |
"if=/dev/zero", "of=%s" % dev_path,
|
|
991 |
"bs=1048576", "count=128", "oflag=direct"])
|
|
992 |
if result.failed:
|
|
993 |
base.ThrowError("Can't wipe the meta device: %s", result.output)
|
|
994 |
|
|
995 |
info = DRBD8.GetProcInfo()
|
|
996 |
cmd_gen = DRBD8.GetCmdGenerator(info)
|
|
997 |
cmd = cmd_gen.GenInitMetaCmd(minor, dev_path)
|
|
998 |
|
|
999 |
result = utils.RunCmd(cmd)
|
|
1000 |
if result.failed:
|
|
1001 |
base.ThrowError("Can't initialize meta device: %s", result.output)
|
|
1002 |
|
888 |
1003 |
@classmethod
|
889 |
1004 |
def Create(cls, unique_id, children, size, params, excl_stor):
|
890 |
1005 |
"""Create a new DRBD8 device.
|
... | ... | |
901 |
1016 |
# check that the minor is unused
|
902 |
1017 |
aminor = unique_id[4]
|
903 |
1018 |
|
904 |
|
drbd_info = DRBD8Info.CreateFromFile()
|
905 |
|
if drbd_info.HasMinorStatus(aminor):
|
906 |
|
status = drbd_info.GetMinorStatus(aminor)
|
|
1019 |
info = DRBD8.GetProcInfo()
|
|
1020 |
if info.HasMinorStatus(aminor):
|
|
1021 |
status = info.GetMinorStatus(aminor)
|
907 |
1022 |
in_use = status.is_in_use
|
908 |
1023 |
else:
|
909 |
1024 |
in_use = False
|
... | ... | |
919 |
1034 |
cls._InitMeta(aminor, meta.dev_path)
|
920 |
1035 |
return cls(unique_id, children, size, params)
|
921 |
1036 |
|
922 |
|
def Grow(self, amount, dryrun, backingstore):
|
923 |
|
"""Resize the DRBD device and its backing storage.
|
924 |
|
|
925 |
|
"""
|
926 |
|
if self.minor is None:
|
927 |
|
base.ThrowError("drbd%d: Grow called while not attached", self._aminor)
|
928 |
|
if len(self._children) != 2 or None in self._children:
|
929 |
|
base.ThrowError("drbd%d: cannot grow diskless device", self.minor)
|
930 |
|
self._children[0].Grow(amount, dryrun, backingstore)
|
931 |
|
if dryrun or backingstore:
|
932 |
|
# DRBD does not support dry-run mode and is not backing storage,
|
933 |
|
# so we'll return here
|
934 |
|
return
|
935 |
|
cmd = self._cmd_gen.GenResizeCmd(self.minor, self.size + amount)
|
936 |
|
result = utils.RunCmd(cmd)
|
937 |
|
if result.failed:
|
938 |
|
base.ThrowError("drbd%d: resize failed: %s", self.minor, result.output)
|
939 |
|
|
940 |
1037 |
|
941 |
1038 |
def _CanReadDevice(path):
|
942 |
1039 |
"""Check if we can read from the given device.
|
943 |
1040 |
|
944 |
1041 |
This tries to read the first 128k of the device.
|
945 |
1042 |
|
|
1043 |
@type path: string
|
|
1044 |
|
946 |
1045 |
"""
|
947 |
1046 |
try:
|
948 |
1047 |
utils.ReadFile(path, size=_DEVICE_READ_SIZE)
|