root / lib / block / base.py @ d01e51a5
History | View | Annotate | Download (11 kB)
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 |
"""Block device abstraction - base class and utility functions"""
|
23 |
|
24 |
import logging |
25 |
|
26 |
from ganeti import objects |
27 |
from ganeti import constants |
28 |
from ganeti import utils |
29 |
from ganeti import errors |
30 |
|
31 |
|
32 |
class BlockDev(object): |
33 |
"""Block device abstract class.
|
34 |
|
35 |
A block device can be in the following states:
|
36 |
- not existing on the system, and by `Create()` it goes into:
|
37 |
- existing but not setup/not active, and by `Assemble()` goes into:
|
38 |
- active read-write and by `Open()` it goes into
|
39 |
- online (=used, or ready for use)
|
40 |
|
41 |
A device can also be online but read-only, however we are not using
|
42 |
the readonly state (LV has it, if needed in the future) and we are
|
43 |
usually looking at this like at a stack, so it's easier to
|
44 |
conceptualise the transition from not-existing to online and back
|
45 |
like a linear one.
|
46 |
|
47 |
The many different states of the device are due to the fact that we
|
48 |
need to cover many device types:
|
49 |
- logical volumes are created, lvchange -a y $lv, and used
|
50 |
- drbd devices are attached to a local disk/remote peer and made primary
|
51 |
|
52 |
A block device is identified by three items:
|
53 |
- the /dev path of the device (dynamic)
|
54 |
- a unique ID of the device (static)
|
55 |
- it's major/minor pair (dynamic)
|
56 |
|
57 |
Not all devices implement both the first two as distinct items. LVM
|
58 |
logical volumes have their unique ID (the pair volume group, logical
|
59 |
volume name) in a 1-to-1 relation to the dev path. For DRBD devices,
|
60 |
the /dev path is again dynamic and the unique id is the pair (host1,
|
61 |
dev1), (host2, dev2).
|
62 |
|
63 |
You can get to a device in two ways:
|
64 |
- creating the (real) device, which returns you
|
65 |
an attached instance (lvcreate)
|
66 |
- attaching of a python instance to an existing (real) device
|
67 |
|
68 |
The second point, the attachment to a device, is different
|
69 |
depending on whether the device is assembled or not. At init() time,
|
70 |
we search for a device with the same unique_id as us. If found,
|
71 |
good. It also means that the device is already assembled. If not,
|
72 |
after assembly we'll have our correct major/minor.
|
73 |
|
74 |
"""
|
75 |
def __init__(self, unique_id, children, size, params): |
76 |
self._children = children
|
77 |
self.dev_path = None |
78 |
self.unique_id = unique_id
|
79 |
self.major = None |
80 |
self.minor = None |
81 |
self.attached = False |
82 |
self.size = size
|
83 |
self.params = params
|
84 |
|
85 |
def Assemble(self): |
86 |
"""Assemble the device from its components.
|
87 |
|
88 |
Implementations of this method by child classes must ensure that:
|
89 |
- after the device has been assembled, it knows its major/minor
|
90 |
numbers; this allows other devices (usually parents) to probe
|
91 |
correctly for their children
|
92 |
- calling this method on an existing, in-use device is safe
|
93 |
- if the device is already configured (and in an OK state),
|
94 |
this method is idempotent
|
95 |
|
96 |
"""
|
97 |
pass
|
98 |
|
99 |
def Attach(self): |
100 |
"""Find a device which matches our config and attach to it.
|
101 |
|
102 |
"""
|
103 |
raise NotImplementedError |
104 |
|
105 |
def Close(self): |
106 |
"""Notifies that the device will no longer be used for I/O.
|
107 |
|
108 |
"""
|
109 |
raise NotImplementedError |
110 |
|
111 |
@classmethod
|
112 |
def Create(cls, unique_id, children, size, params, excl_stor): |
113 |
"""Create the device.
|
114 |
|
115 |
If the device cannot be created, it will return None
|
116 |
instead. Error messages go to the logging system.
|
117 |
|
118 |
Note that for some devices, the unique_id is used, and for other,
|
119 |
the children. The idea is that these two, taken together, are
|
120 |
enough for both creation and assembly (later).
|
121 |
|
122 |
"""
|
123 |
raise NotImplementedError |
124 |
|
125 |
def Remove(self): |
126 |
"""Remove this device.
|
127 |
|
128 |
This makes sense only for some of the device types: LV and file
|
129 |
storage. Also note that if the device can't attach, the removal
|
130 |
can't be completed.
|
131 |
|
132 |
"""
|
133 |
raise NotImplementedError |
134 |
|
135 |
def Rename(self, new_id): |
136 |
"""Rename this device.
|
137 |
|
138 |
This may or may not make sense for a given device type.
|
139 |
|
140 |
"""
|
141 |
raise NotImplementedError |
142 |
|
143 |
def Open(self, force=False): |
144 |
"""Make the device ready for use.
|
145 |
|
146 |
This makes the device ready for I/O. For now, just the DRBD
|
147 |
devices need this.
|
148 |
|
149 |
The force parameter signifies that if the device has any kind of
|
150 |
--force thing, it should be used, we know what we are doing.
|
151 |
|
152 |
"""
|
153 |
raise NotImplementedError |
154 |
|
155 |
def Shutdown(self): |
156 |
"""Shut down the device, freeing its children.
|
157 |
|
158 |
This undoes the `Assemble()` work, except for the child
|
159 |
assembling; as such, the children on the device are still
|
160 |
assembled after this call.
|
161 |
|
162 |
"""
|
163 |
raise NotImplementedError |
164 |
|
165 |
def SetSyncParams(self, params): |
166 |
"""Adjust the synchronization parameters of the mirror.
|
167 |
|
168 |
In case this is not a mirroring device, this is no-op.
|
169 |
|
170 |
@param params: dictionary of LD level disk parameters related to the
|
171 |
synchronization.
|
172 |
@rtype: list
|
173 |
@return: a list of error messages, emitted both by the current node and by
|
174 |
children. An empty list means no errors.
|
175 |
|
176 |
"""
|
177 |
result = [] |
178 |
if self._children: |
179 |
for child in self._children: |
180 |
result.extend(child.SetSyncParams(params)) |
181 |
return result
|
182 |
|
183 |
def PauseResumeSync(self, pause): |
184 |
"""Pause/Resume the sync of the mirror.
|
185 |
|
186 |
In case this is not a mirroring device, this is no-op.
|
187 |
|
188 |
@param pause: Whether to pause or resume
|
189 |
|
190 |
"""
|
191 |
result = True
|
192 |
if self._children: |
193 |
for child in self._children: |
194 |
result = result and child.PauseResumeSync(pause)
|
195 |
return result
|
196 |
|
197 |
def GetSyncStatus(self): |
198 |
"""Returns the sync status of the device.
|
199 |
|
200 |
If this device is a mirroring device, this function returns the
|
201 |
status of the mirror.
|
202 |
|
203 |
If sync_percent is None, it means the device is not syncing.
|
204 |
|
205 |
If estimated_time is None, it means we can't estimate
|
206 |
the time needed, otherwise it's the time left in seconds.
|
207 |
|
208 |
If is_degraded is True, it means the device is missing
|
209 |
redundancy. This is usually a sign that something went wrong in
|
210 |
the device setup, if sync_percent is None.
|
211 |
|
212 |
The ldisk parameter represents the degradation of the local
|
213 |
data. This is only valid for some devices, the rest will always
|
214 |
return False (not degraded).
|
215 |
|
216 |
@rtype: objects.BlockDevStatus
|
217 |
|
218 |
"""
|
219 |
return objects.BlockDevStatus(dev_path=self.dev_path, |
220 |
major=self.major,
|
221 |
minor=self.minor,
|
222 |
sync_percent=None,
|
223 |
estimated_time=None,
|
224 |
is_degraded=False,
|
225 |
ldisk_status=constants.LDS_OKAY) |
226 |
|
227 |
def CombinedSyncStatus(self): |
228 |
"""Calculate the mirror status recursively for our children.
|
229 |
|
230 |
The return value is the same as for `GetSyncStatus()` except the
|
231 |
minimum percent and maximum time are calculated across our
|
232 |
children.
|
233 |
|
234 |
@rtype: objects.BlockDevStatus
|
235 |
|
236 |
"""
|
237 |
status = self.GetSyncStatus()
|
238 |
|
239 |
min_percent = status.sync_percent |
240 |
max_time = status.estimated_time |
241 |
is_degraded = status.is_degraded |
242 |
ldisk_status = status.ldisk_status |
243 |
|
244 |
if self._children: |
245 |
for child in self._children: |
246 |
child_status = child.GetSyncStatus() |
247 |
|
248 |
if min_percent is None: |
249 |
min_percent = child_status.sync_percent |
250 |
elif child_status.sync_percent is not None: |
251 |
min_percent = min(min_percent, child_status.sync_percent)
|
252 |
|
253 |
if max_time is None: |
254 |
max_time = child_status.estimated_time |
255 |
elif child_status.estimated_time is not None: |
256 |
max_time = max(max_time, child_status.estimated_time)
|
257 |
|
258 |
is_degraded = is_degraded or child_status.is_degraded
|
259 |
|
260 |
if ldisk_status is None: |
261 |
ldisk_status = child_status.ldisk_status |
262 |
elif child_status.ldisk_status is not None: |
263 |
ldisk_status = max(ldisk_status, child_status.ldisk_status)
|
264 |
|
265 |
return objects.BlockDevStatus(dev_path=self.dev_path, |
266 |
major=self.major,
|
267 |
minor=self.minor,
|
268 |
sync_percent=min_percent, |
269 |
estimated_time=max_time, |
270 |
is_degraded=is_degraded, |
271 |
ldisk_status=ldisk_status) |
272 |
|
273 |
def SetInfo(self, text): |
274 |
"""Update metadata with info text.
|
275 |
|
276 |
Only supported for some device types.
|
277 |
|
278 |
"""
|
279 |
for child in self._children: |
280 |
child.SetInfo(text) |
281 |
|
282 |
def Grow(self, amount, dryrun, backingstore): |
283 |
"""Grow the block device.
|
284 |
|
285 |
@type amount: integer
|
286 |
@param amount: the amount (in mebibytes) to grow with
|
287 |
@type dryrun: boolean
|
288 |
@param dryrun: whether to execute the operation in simulation mode
|
289 |
only, without actually increasing the size
|
290 |
@param backingstore: whether to execute the operation on backing storage
|
291 |
only, or on "logical" storage only; e.g. DRBD is logical storage,
|
292 |
whereas LVM, file, RBD are backing storage
|
293 |
|
294 |
"""
|
295 |
raise NotImplementedError |
296 |
|
297 |
def GetActualSize(self): |
298 |
"""Return the actual disk size.
|
299 |
|
300 |
@note: the device needs to be active when this is called
|
301 |
|
302 |
"""
|
303 |
assert self.attached, "BlockDevice not attached in GetActualSize()" |
304 |
result = utils.RunCmd(["blockdev", "--getsize64", self.dev_path]) |
305 |
if result.failed:
|
306 |
ThrowError("blockdev failed (%s): %s",
|
307 |
result.fail_reason, result.output) |
308 |
try:
|
309 |
sz = int(result.output.strip())
|
310 |
except (ValueError, TypeError), err: |
311 |
ThrowError("Failed to parse blockdev output: %s", str(err)) |
312 |
return sz
|
313 |
|
314 |
def __repr__(self): |
315 |
return ("<%s: unique_id: %s, children: %s, %s:%s, %s>" % |
316 |
(self.__class__, self.unique_id, self._children, |
317 |
self.major, self.minor, self.dev_path)) |
318 |
|
319 |
|
320 |
def ThrowError(msg, *args): |
321 |
"""Log an error to the node daemon and the raise an exception.
|
322 |
|
323 |
@type msg: string
|
324 |
@param msg: the text of the exception
|
325 |
@raise errors.BlockDeviceError
|
326 |
|
327 |
"""
|
328 |
if args:
|
329 |
msg = msg % args |
330 |
logging.error(msg) |
331 |
raise errors.BlockDeviceError(msg)
|
332 |
|
333 |
|
334 |
def IgnoreError(fn, *args, **kwargs): |
335 |
"""Executes the given function, ignoring BlockDeviceErrors.
|
336 |
|
337 |
This is used in order to simplify the execution of cleanup or
|
338 |
rollback functions.
|
339 |
|
340 |
@rtype: boolean
|
341 |
@return: True when fn didn't raise an exception, False otherwise
|
342 |
|
343 |
"""
|
344 |
try:
|
345 |
fn(*args, **kwargs) |
346 |
return True |
347 |
except errors.BlockDeviceError, err:
|
348 |
logging.warning("Caught BlockDeviceError but ignoring: %s", str(err)) |
349 |
return False |