Coverage for nova/virt/disk/mount/nbd.py: 92%

83 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-04-24 11:16 +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.""" 

15 

16import os 

17import random 

18import re 

19import time 

20 

21from oslo_concurrency import processutils 

22from oslo_log import log as logging 

23 

24import nova.conf 

25from nova.i18n import _ 

26import nova.privsep.fs 

27from nova import utils 

28from nova.virt.disk.mount import api 

29 

30LOG = logging.getLogger(__name__) 

31 

32CONF = nova.conf.CONF 

33 

34NBD_DEVICE_RE = re.compile('nbd[0-9]+') 

35 

36 

37class NbdMount(api.Mount): 

38 """qemu-nbd support disk images.""" 

39 mode = 'nbd' 

40 

41 @staticmethod 

42 def _detect_nbd_devices(): 

43 """Detect nbd device files.""" 

44 return list(filter(NBD_DEVICE_RE.match, os.listdir('/sys/block/'))) 

45 

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 

56 

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 

62 

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) 

71 

72 @utils.synchronized('nbd-allocation-lock') 

73 def _inner_get_dev(self): 

74 device = self._allocate_nbd() 

75 if not device: 

76 return False 

77 

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) 

86 

87 if err: 

88 self.error = _('qemu-nbd error: %s') % err 

89 LOG.info('NBD mount error: %s', self.error) 

90 return False 

91 

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) 

103 

104 # Cleanup 

105 try: 

106 _out, err = nova.privsep.fs.nbd_disconnect(device) 

107 except processutils.ProcessExecutionError as exc: 

108 err = str(exc) 

109 

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 

114 

115 self.error = '' 

116 self.linked = True 

117 return True 

118 

119 def get_dev(self): 

120 """Retry requests for NBD devices.""" 

121 return self._get_dev_retry_helper() 

122 

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 

130 

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)