1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
|
# Copyright 2011 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Support for mounting images with qemu-nbd."""
import os
import random
import re
import time
from oslo.config import cfg
from nova.i18n import _
from nova.openstack.common import log as logging
from nova import utils
from nova.virt.disk.mount import api
LOG = logging.getLogger(__name__)
nbd_opts = [
cfg.IntOpt('timeout_nbd',
default=10,
help='Amount of time, in seconds, to wait for NBD '
'device start up.'),
]
CONF = cfg.CONF
CONF.register_opts(nbd_opts)
NBD_DEVICE_RE = re.compile('nbd[0-9]+')
class NbdMount(api.Mount):
"""qemu-nbd support disk images."""
mode = 'nbd'
def _detect_nbd_devices(self):
"""Detect nbd device files."""
return filter(NBD_DEVICE_RE.match, os.listdir('/sys/block/'))
def _find_unused(self, devices):
for device in devices:
if not os.path.exists(os.path.join('/sys/block/', device, 'pid')):
if not os.path.exists('/var/lock/qemu-nbd-%s' % device):
return device
else:
LOG.error(_('NBD error - previous umount did not cleanup '
'/var/lock/qemu-nbd-%s.'), device)
LOG.warn(_('No free nbd devices'))
return None
def _allocate_nbd(self):
if not os.path.exists('/sys/block/nbd0'):
LOG.error(_('nbd module not loaded'))
self.error = _('nbd unavailable: module not loaded')
return None
devices = self._detect_nbd_devices()
random.shuffle(devices)
device = self._find_unused(devices)
if not device:
# really want to log this info, not raise
self.error = _('No free nbd devices')
return None
return os.path.join('/dev', device)
@utils.synchronized('nbd-allocation-lock')
def _inner_get_dev(self):
device = self._allocate_nbd()
if not device:
return False
# NOTE(mikal): qemu-nbd will return an error if the device file is
# already in use.
LOG.debug('Get nbd device %(dev)s for %(imgfile)s',
{'dev': device, 'imgfile': self.image})
_out, err = utils.trycmd('qemu-nbd', '-c', device, self.image,
run_as_root=True)
if err:
self.error = _('qemu-nbd error: %s') % err
LOG.info(_('NBD mount error: %s'), self.error)
return False
# NOTE(vish): this forks into another process, so give it a chance
# to set up before continuing
pidfile = "/sys/block/%s/pid" % os.path.basename(device)
for _i in range(CONF.timeout_nbd):
if os.path.exists(pidfile):
self.device = device
break
time.sleep(1)
else:
self.error = _('nbd device %s did not show up') % device
LOG.info(_('NBD mount error: %s'), self.error)
# Cleanup
_out, err = utils.trycmd('qemu-nbd', '-d', device,
run_as_root=True)
if err:
LOG.warn(_('Detaching from erroneous nbd device returned '
'error: %s'), err)
return False
self.error = ''
self.linked = True
return True
def get_dev(self):
"""Retry requests for NBD devices."""
return self._get_dev_retry_helper()
def unget_dev(self):
if not self.linked:
return
LOG.debug('Release nbd device %s', self.device)
utils.execute('qemu-nbd', '-d', self.device, run_as_root=True)
self.linked = False
self.device = None
def flush_dev(self):
"""flush NBD block device buffer."""
# Perform an explicit BLKFLSBUF to support older qemu-nbd(s).
# Without this flush, when a nbd device gets re-used the
# qemu-nbd intermittently hangs.
if self.device:
utils.execute('blockdev', '--flushbufs',
self.device, run_as_root=True)
|