Coverage for nova/virt/libvirt/volume/quobyte.py: 94%

117 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-04-24 11:16 +0000

1# Copyright (c) 2015 Quobyte Inc. 

2# All Rights Reserved. 

3# 

4# Licensed under the Apache License, Version 2.0 (the "License"); you may 

5# not use this file except in compliance with the License. You may obtain 

6# a copy of the License at 

7# 

8# http://www.apache.org/licenses/LICENSE-2.0 

9# 

10# Unless required by applicable law or agreed to in writing, software 

11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 

12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 

13# License for the specific language governing permissions and limitations 

14# under the License. 

15 

16import os 

17import warnings 

18 

19from oslo_concurrency import processutils 

20from oslo_log import log as logging 

21from oslo_utils import fileutils 

22import psutil 

23 

24import nova.conf 

25from nova import exception as nova_exception 

26from nova.i18n import _ 

27import nova.privsep.libvirt 

28from nova import utils 

29from nova.virt.libvirt.volume import fs 

30 

31LOG = logging.getLogger(__name__) 

32 

33CONF = nova.conf.CONF 

34 

35SOURCE_PROTOCOL = 'quobyte' 

36SOURCE_TYPE = 'file' 

37DRIVER_CACHE = 'none' 

38DRIVER_IO = 'native' 

39VALID_SYSD_STATES = ["starting", "running", "degraded"] 

40SYSTEMCTL_CHECK_PATH = "/run/systemd/system" 

41 

42 

43_is_systemd = None 

44 

45 

46def is_systemd(): 

47 """Checks if the host is running systemd""" 

48 global _is_systemd 

49 

50 if _is_systemd is not None: 

51 return _is_systemd 

52 

53 tmp_is_systemd = False 

54 

55 if psutil.Process(1).name() == "systemd" or os.path.exists( 

56 SYSTEMCTL_CHECK_PATH): 

57 # NOTE(kaisers): exit code might be >1 in theory but in practice this 

58 # is hard coded to 1. Due to backwards compatibility and systemd 

59 # CODING_STYLE this is unlikely to change. 

60 sysdout, sysderr = processutils.execute("systemctl", 

61 "is-system-running", 

62 check_exit_code=[0, 1]) 

63 for state in VALID_SYSD_STATES: 

64 if state == sysdout.strip(): 

65 tmp_is_systemd = True 

66 break 

67 

68 _is_systemd = tmp_is_systemd 

69 return _is_systemd 

70 

71 

72def mount_volume(volume, mnt_base, configfile=None): 

73 """Wraps execute calls for mounting a Quobyte volume""" 

74 fileutils.ensure_tree(mnt_base) 

75 

76 # Note(kaisers): with systemd this requires a separate CGROUP to 

77 # prevent Nova service stop/restarts from killing the mount. 

78 if is_systemd(): 

79 LOG.debug('Mounting volume %s at mount point %s via systemd-run', 

80 volume, mnt_base) 

81 nova.privsep.libvirt.systemd_run_qb_mount(volume, mnt_base, 

82 cfg_file=configfile) 

83 else: 

84 LOG.debug('Mounting volume %s at mount point %s via mount.quobyte', 

85 volume, mnt_base, cfg_file=configfile) 

86 

87 nova.privsep.libvirt.unprivileged_qb_mount(volume, mnt_base, 

88 cfg_file=configfile) 

89 LOG.info('Mounted volume: %s', volume) 

90 

91 

92def umount_volume(mnt_base): 

93 """Wraps execute calls for unmouting a Quobyte volume""" 

94 try: 

95 if is_systemd(): 

96 nova.privsep.libvirt.umount(mnt_base) 

97 else: 

98 nova.privsep.libvirt.unprivileged_umount(mnt_base) 

99 except processutils.ProcessExecutionError as exc: 

100 if 'Device or resource busy' in str(exc): 

101 LOG.error("The Quobyte volume at %s is still in use.", mnt_base) 

102 else: 

103 LOG.exception("Couldn't unmount the Quobyte Volume at %s", 

104 mnt_base) 

105 

106 

107def validate_volume(mount_path): 

108 """Determine if the volume is a valid Quobyte mount. 

109 

110 Runs a number of tests to be sure this is a (working) Quobyte mount 

111 """ 

112 partitions = psutil.disk_partitions(all=True) 

113 for p in partitions: 

114 if mount_path != p.mountpoint: 114 ↛ 115line 114 didn't jump to line 115 because the condition on line 114 was never true

115 continue 

116 if p.device.startswith("quobyte@") or p.fstype == "fuse.quobyte": 

117 statresult = os.stat(mount_path) 

118 # Note(kaisers): Quobyte always shows mount points with size 0 

119 if statresult.st_size == 0: 

120 # client looks healthy 

121 return # we're happy here 

122 else: 

123 msg = (_("The mount %(mount_path)s is not a " 

124 "valid Quobyte volume. Stale mount?") 

125 % {'mount_path': mount_path}) 

126 raise nova_exception.StaleVolumeMount(msg, mount_path=mount_path) 

127 else: 

128 msg = (_("The mount %(mount_path)s is not a valid " 

129 "Quobyte volume according to partition list.") 

130 % {'mount_path': mount_path}) 

131 raise nova_exception.InvalidVolume(msg) 

132 msg = (_("No matching Quobyte mount entry for %(mount_path)s" 

133 " could be found for validation in partition list.") 

134 % {'mount_path': mount_path}) 

135 raise nova_exception.InvalidVolume(msg) 

136 

137 

138class LibvirtQuobyteVolumeDriver(fs.LibvirtBaseFileSystemVolumeDriver): 

139 """Class implements libvirt part of volume driver for Quobyte.""" 

140 

141 def __init__(self, host): 

142 super(LibvirtQuobyteVolumeDriver, self).__init__(host) 

143 warnings.warn('Quobyte volume support is deprecated', 

144 category=DeprecationWarning, stacklevel=2) 

145 

146 def _get_mount_point_base(self): 

147 return CONF.libvirt.quobyte_mount_point_base 

148 

149 def get_config(self, connection_info, disk_info): 

150 conf = super(LibvirtQuobyteVolumeDriver, 

151 self).get_config(connection_info, disk_info) 

152 data = connection_info['data'] 

153 conf.source_protocol = SOURCE_PROTOCOL 

154 conf.source_type = SOURCE_TYPE 

155 conf.driver_cache = DRIVER_CACHE 

156 conf.driver_io = DRIVER_IO 

157 conf.driver_format = data.get('format', 'raw') 

158 

159 conf.source_path = self._get_device_path(connection_info) 

160 

161 return conf 

162 

163 @utils.synchronized('connect_qb_volume') 

164 def connect_volume(self, connection_info, instance): 

165 """Connect the volume.""" 

166 if is_systemd(): 166 ↛ 169line 166 didn't jump to line 169 because the condition on line 166 was always true

167 LOG.debug("systemd detected.", instance=instance) 

168 else: 

169 LOG.debug("No systemd detected.", instance=instance) 

170 

171 data = connection_info['data'] 

172 quobyte_volume = self._normalize_export(data['export']) 

173 mount_path = self._get_mount_path(connection_info) 

174 try: 

175 validate_volume(mount_path) 

176 mounted = True 

177 except nova_exception.StaleVolumeMount: 

178 mounted = False 

179 LOG.info('Fixing previous mount %s which was not ' 

180 'unmounted correctly.', mount_path, instance=instance) 

181 umount_volume(mount_path) 

182 except nova_exception.InvalidVolume: 

183 mounted = False 

184 

185 if not mounted: 

186 mount_volume(quobyte_volume, 

187 mount_path, 

188 CONF.libvirt.quobyte_client_cfg) 

189 

190 try: 

191 validate_volume(mount_path) 

192 except (nova_exception.InvalidVolume, 

193 nova_exception.StaleVolumeMount) as nex: 

194 LOG.error("Could not mount Quobyte volume: %s", nex, 

195 instance=instance) 

196 

197 @utils.synchronized('connect_qb_volume') 

198 def disconnect_volume(self, connection_info, instance, force=False): 

199 """Disconnect the volume.""" 

200 

201 mount_path = self._get_mount_path(connection_info) 

202 try: 

203 validate_volume(mount_path) 

204 except (nova_exception.InvalidVolume, 

205 nova_exception.StaleVolumeMount) as exc: 

206 LOG.warning("Could not disconnect Quobyte volume mount: %s", exc, 

207 instance=instance) 

208 else: 

209 umount_volume(mount_path) 

210 

211 def _normalize_export(self, export): 

212 protocol = SOURCE_PROTOCOL + "://" 

213 if export.startswith(protocol): 

214 export = export[len(protocol):] 

215 return export