Coverage for nova/virt/disk/mount/nbd.py: 92%
83 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-04-17 15:08 +0000
« prev ^ index » next coverage.py v7.6.12, created at 2025-04-17 15:08 +0000
1# Copyright 2011 Red Hat, Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may
4# not use this file except in compliance with the License. You may obtain
5# a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
14"""Support for mounting images with qemu-nbd."""
16import os
17import random
18import re
19import time
21from oslo_concurrency import processutils
22from oslo_log import log as logging
24import nova.conf
25from nova.i18n import _
26import nova.privsep.fs
27from nova import utils
28from nova.virt.disk.mount import api
30LOG = logging.getLogger(__name__)
32CONF = nova.conf.CONF
34NBD_DEVICE_RE = re.compile('nbd[0-9]+')
37class NbdMount(api.Mount):
38 """qemu-nbd support disk images."""
39 mode = 'nbd'
41 @staticmethod
42 def _detect_nbd_devices():
43 """Detect nbd device files."""
44 return list(filter(NBD_DEVICE_RE.match, os.listdir('/sys/block/')))
46 def _find_unused(self, devices):
47 for device in devices:
48 if not os.path.exists(os.path.join('/sys/block/', device, 'pid')):
49 if not os.path.exists('/var/lock/qemu-nbd-%s' % device): 49 ↛ 52line 49 didn't jump to line 52 because the condition on line 49 was always true
50 return device
51 else:
52 LOG.error('NBD error - previous umount did not '
53 'cleanup /var/lock/qemu-nbd-%s.', device)
54 LOG.warning('No free nbd devices')
55 return None
57 def _allocate_nbd(self):
58 if not os.path.exists('/sys/block/nbd0'):
59 LOG.error('nbd module not loaded')
60 self.error = _('nbd unavailable: module not loaded')
61 return None
63 devices = self._detect_nbd_devices()
64 random.shuffle(devices)
65 device = self._find_unused(devices)
66 if not device:
67 # really want to log this info, not raise
68 self.error = _('No free nbd devices')
69 return None
70 return os.path.join('/dev', device)
72 @utils.synchronized('nbd-allocation-lock')
73 def _inner_get_dev(self):
74 device = self._allocate_nbd()
75 if not device:
76 return False
78 # NOTE(mikal): qemu-nbd will return an error if the device file is
79 # already in use.
80 LOG.debug('Get nbd device %(dev)s for %(imgfile)s',
81 {'dev': device, 'imgfile': self.image.path})
82 try:
83 _out, err = nova.privsep.fs.nbd_connect(device, self.image.path)
84 except processutils.ProcessExecutionError as exc:
85 err = str(exc)
87 if err:
88 self.error = _('qemu-nbd error: %s') % err
89 LOG.info('NBD mount error: %s', self.error)
90 return False
92 # NOTE(vish): this forks into another process, so give it a chance
93 # to set up before continuing
94 pidfile = "/sys/block/%s/pid" % os.path.basename(device)
95 for _i in range(CONF.timeout_nbd):
96 if os.path.exists(pidfile):
97 self.device = device
98 break
99 time.sleep(1)
100 else:
101 self.error = _('nbd device %s did not show up') % device
102 LOG.info('NBD mount error: %s', self.error)
104 # Cleanup
105 try:
106 _out, err = nova.privsep.fs.nbd_disconnect(device)
107 except processutils.ProcessExecutionError as exc:
108 err = str(exc)
110 if err: 110 ↛ 111line 110 didn't jump to line 111 because the condition on line 110 was never true
111 LOG.warning('Detaching from erroneous nbd device returned '
112 'error: %s', err)
113 return False
115 self.error = ''
116 self.linked = True
117 return True
119 def get_dev(self):
120 """Retry requests for NBD devices."""
121 return self._get_dev_retry_helper()
123 def unget_dev(self):
124 if not self.linked:
125 return
126 LOG.debug('Release nbd device %s', self.device)
127 nova.privsep.fs.nbd_disconnect(self.device)
128 self.linked = False
129 self.device = None
131 def flush_dev(self):
132 """flush NBD block device buffer."""
133 # Perform an explicit BLKFLSBUF to support older qemu-nbd(s).
134 # Without this flush, when a nbd device gets re-used the
135 # qemu-nbd intermittently hangs.
136 if self.device: 136 ↛ exitline 136 didn't return from function 'flush_dev' because the condition on line 136 was always true
137 nova.privsep.fs.blockdev_flush(self.device)