Coverage for nova/virt/libvirt/volume/quobyte.py: 94%
117 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 (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.
16import os
17import warnings
19from oslo_concurrency import processutils
20from oslo_log import log as logging
21from oslo_utils import fileutils
22import psutil
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
31LOG = logging.getLogger(__name__)
33CONF = nova.conf.CONF
35SOURCE_PROTOCOL = 'quobyte'
36SOURCE_TYPE = 'file'
37DRIVER_CACHE = 'none'
38DRIVER_IO = 'native'
39VALID_SYSD_STATES = ["starting", "running", "degraded"]
40SYSTEMCTL_CHECK_PATH = "/run/systemd/system"
43_is_systemd = None
46def is_systemd():
47 """Checks if the host is running systemd"""
48 global _is_systemd
50 if _is_systemd is not None:
51 return _is_systemd
53 tmp_is_systemd = False
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
68 _is_systemd = tmp_is_systemd
69 return _is_systemd
72def mount_volume(volume, mnt_base, configfile=None):
73 """Wraps execute calls for mounting a Quobyte volume"""
74 fileutils.ensure_tree(mnt_base)
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)
87 nova.privsep.libvirt.unprivileged_qb_mount(volume, mnt_base,
88 cfg_file=configfile)
89 LOG.info('Mounted volume: %s', volume)
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)
107def validate_volume(mount_path):
108 """Determine if the volume is a valid Quobyte mount.
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)
138class LibvirtQuobyteVolumeDriver(fs.LibvirtBaseFileSystemVolumeDriver):
139 """Class implements libvirt part of volume driver for Quobyte."""
141 def __init__(self, host):
142 super(LibvirtQuobyteVolumeDriver, self).__init__(host)
143 warnings.warn('Quobyte volume support is deprecated',
144 category=DeprecationWarning, stacklevel=2)
146 def _get_mount_point_base(self):
147 return CONF.libvirt.quobyte_mount_point_base
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')
159 conf.source_path = self._get_device_path(connection_info)
161 return conf
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)
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
185 if not mounted:
186 mount_volume(quobyte_volume,
187 mount_path,
188 CONF.libvirt.quobyte_client_cfg)
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)
197 @utils.synchronized('connect_qb_volume')
198 def disconnect_volume(self, connection_info, instance, force=False):
199 """Disconnect the volume."""
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)
211 def _normalize_export(self, export):
212 protocol = SOURCE_PROTOCOL + "://"
213 if export.startswith(protocol):
214 export = export[len(protocol):]
215 return export