Coverage for nova/virt/libvirt/volume/remotefs.py: 84%
108 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-04-24 11:16 +0000
« prev ^ index » next coverage.py v7.6.12, created at 2025-04-24 11:16 +0000
1# Copyright 2014 Cloudbase Solutions Srl
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 abc
17import os
19from oslo_concurrency import processutils
20from oslo_log import log as logging
21from oslo_utils import fileutils
22from oslo_utils import importutils
24import nova.conf
25import nova.privsep.fs
26from nova import utils
28LOG = logging.getLogger(__name__)
30CONF = nova.conf.CONF
33def mount_share(mount_path, export_path,
34 export_type, options=None):
35 """Mount a remote export to mount_path.
37 :param mount_path: place where the remote export will be mounted
38 :param export_path: path of the export to be mounted
39 :export_type: remote export type (e.g. cifs, nfs, etc.)
40 :options: A list containing mount options
41 """
42 fileutils.ensure_tree(mount_path)
44 try:
45 nova.privsep.fs.mount(export_type, export_path, mount_path, options)
46 except processutils.ProcessExecutionError as exc:
47 if 'Device or resource busy' in str(exc):
48 LOG.warning("%s is already mounted", export_path)
49 else:
50 raise
53def unmount_share(mount_path, export_path):
54 """Unmount a remote share.
56 :param mount_path: remote export mount point
57 :param export_path: path of the remote export to be unmounted
58 """
59 try:
60 nova.privsep.fs.umount(mount_path)
61 except processutils.ProcessExecutionError as exc:
62 if 'target is busy' in str(exc):
63 LOG.debug("The share %s is still in use.", export_path)
64 else:
65 LOG.exception("Couldn't unmount the share %s", export_path)
68class RemoteFilesystem(object):
69 """Represents actions that can be taken on a remote host's filesystem."""
71 def __init__(self):
72 transport = CONF.libvirt.remote_filesystem_transport
73 cls_name = '.'.join([__name__, transport.capitalize()])
74 cls_name += 'Driver'
75 self.driver = importutils.import_object(cls_name)
77 def create_file(self, host, dst_path, on_execute=None,
78 on_completion=None):
79 LOG.debug("Creating file %s on remote host %s", dst_path, host)
80 self.driver.create_file(host, dst_path, on_execute=on_execute,
81 on_completion=on_completion)
83 def remove_file(self, host, dst_path, on_execute=None,
84 on_completion=None):
85 LOG.debug("Removing file %s on remote host %s", dst_path, host)
86 self.driver.remove_file(host, dst_path, on_execute=on_execute,
87 on_completion=on_completion)
89 def create_dir(self, host, dst_path, on_execute=None,
90 on_completion=None):
91 LOG.debug("Creating directory %s on remote host %s", dst_path, host)
92 self.driver.create_dir(host, dst_path, on_execute=on_execute,
93 on_completion=on_completion)
95 def remove_dir(self, host, dst_path, on_execute=None,
96 on_completion=None):
97 LOG.debug("Removing directory %s on remote host %s", dst_path, host)
98 self.driver.remove_dir(host, dst_path, on_execute=on_execute,
99 on_completion=on_completion)
101 def copy_file(self, src, dst, on_execute=None,
102 on_completion=None, compression=True):
103 LOG.debug("Copying file %s to %s", src, dst)
104 self.driver.copy_file(src, dst, on_execute=on_execute,
105 on_completion=on_completion,
106 compression=compression)
109class RemoteFilesystemDriver(metaclass=abc.ABCMeta):
110 @abc.abstractmethod
111 def create_file(self, host, dst_path, on_execute, on_completion):
112 """Create file on the remote system.
114 :param host: Remote host
115 :param dst_path: Destination path
116 :param on_execute: Callback method to store pid of process in cache
117 :param on_completion: Callback method to remove pid of process from
118 cache
119 """
121 @abc.abstractmethod
122 def remove_file(self, host, dst_path, on_execute, on_completion):
123 """Removes a file on a remote host.
125 :param host: Remote host
126 :param dst_path: Destination path
127 :param on_execute: Callback method to store pid of process in cache
128 :param on_completion: Callback method to remove pid of process from
129 cache
130 """
132 @abc.abstractmethod
133 def create_dir(self, host, dst_path, on_execute, on_completion):
134 """Create directory on the remote system.
136 :param host: Remote host
137 :param dst_path: Destination path
138 :param on_execute: Callback method to store pid of process in cache
139 :param on_completion: Callback method to remove pid of process from
140 cache
141 """
143 @abc.abstractmethod
144 def remove_dir(self, host, dst_path, on_execute, on_completion):
145 """Removes a directory on a remote host.
147 :param host: Remote host
148 :param dst_path: Destination path
149 :param on_execute: Callback method to store pid of process in cache
150 :param on_completion: Callback method to remove pid of process from
151 cache
152 """
154 @abc.abstractmethod
155 def copy_file(self, src, dst, on_execute, on_completion, compression):
156 """Copy file to/from remote host.
158 Remote address must be specified in format:
159 REM_HOST_IP_ADDRESS:REM_HOST_PATH
160 For example:
161 192.168.1.10:/home/file
163 :param src: Source address
164 :param dst: Destination path
165 :param on_execute: Callback method to store pid of process in cache
166 :param on_completion: Callback method to remove pid of process from
167 :param compression: If true, compress files when copying; drivers may
168 ignore this if compression is not supported
169 """
172class SshDriver(RemoteFilesystemDriver):
174 def create_file(self, host, dst_path, on_execute, on_completion):
175 utils.ssh_execute(host, 'touch', dst_path,
176 on_execute=on_execute, on_completion=on_completion)
178 def remove_file(self, host, dst, on_execute, on_completion):
179 utils.ssh_execute(host, 'rm', dst,
180 on_execute=on_execute, on_completion=on_completion)
182 def create_dir(self, host, dst_path, on_execute, on_completion):
183 utils.ssh_execute(host, 'mkdir', '-p', dst_path,
184 on_execute=on_execute, on_completion=on_completion)
186 def remove_dir(self, host, dst, on_execute, on_completion):
187 utils.ssh_execute(host, 'rm', '-rf', dst,
188 on_execute=on_execute, on_completion=on_completion)
190 def copy_file(self, src, dst, on_execute, on_completion, compression):
191 args = ['scp']
192 if compression:
193 args.append('-C')
194 # As far as ploop disks are in fact directories we add '-r' argument
195 args.extend(['-r', src, dst])
196 processutils.execute(
197 *args, on_execute=on_execute, on_completion=on_completion)
200class RsyncDriver(RemoteFilesystemDriver):
202 def create_file(self, host, dst_path, on_execute, on_completion):
203 with utils.tempdir() as tempdir:
204 dir_path = os.path.dirname(os.path.normpath(dst_path))
206 # Create target dir inside temporary directory
207 local_tmp_dir = os.path.join(tempdir,
208 dir_path.strip(os.path.sep))
209 processutils.execute('mkdir', '-p', local_tmp_dir,
210 on_execute=on_execute,
211 on_completion=on_completion)
213 # Create file in directory
214 file_name = os.path.basename(os.path.normpath(dst_path))
215 local_tmp_file = os.path.join(local_tmp_dir, file_name)
216 processutils.execute('touch', local_tmp_file,
217 on_execute=on_execute,
218 on_completion=on_completion)
219 RsyncDriver._synchronize_object(tempdir,
220 host, dst_path,
221 on_execute=on_execute,
222 on_completion=on_completion)
224 def remove_file(self, host, dst, on_execute, on_completion):
225 with utils.tempdir() as tempdir:
226 RsyncDriver._remove_object(tempdir, host, dst,
227 on_execute=on_execute,
228 on_completion=on_completion)
230 def create_dir(self, host, dst_path, on_execute, on_completion):
231 with utils.tempdir() as tempdir:
232 dir_path = os.path.normpath(dst_path)
234 # Create target dir inside temporary directory
235 local_tmp_dir = os.path.join(tempdir,
236 dir_path.strip(os.path.sep))
237 processutils.execute('mkdir', '-p', local_tmp_dir,
238 on_execute=on_execute,
239 on_completion=on_completion)
240 RsyncDriver._synchronize_object(tempdir,
241 host, dst_path,
242 on_execute=on_execute,
243 on_completion=on_completion)
245 def remove_dir(self, host, dst, on_execute, on_completion):
246 # Remove remote directory's content
247 with utils.tempdir() as tempdir:
248 processutils.execute('rsync', '--archive', '--delete-excluded',
249 tempdir + os.path.sep,
250 utils.format_remote_path(host, dst),
251 on_execute=on_execute,
252 on_completion=on_completion)
254 # Delete empty directory
255 RsyncDriver._remove_object(tempdir, host, dst,
256 on_execute=on_execute,
257 on_completion=on_completion)
259 @staticmethod
260 def _remove_object(src, host, dst, on_execute, on_completion):
261 """Removes a file or empty directory on a remote host.
263 :param src: Empty directory used for rsync purposes
264 :param host: Remote host
265 :param dst: Destination path
266 :param on_execute: Callback method to store pid of process in cache
267 :param on_completion: Callback method to remove pid of process from
268 cache
269 """
270 processutils.execute(
271 'rsync', '--archive', '--delete',
272 '--include', os.path.basename(os.path.normpath(dst)),
273 '--exclude', '*',
274 os.path.normpath(src) + os.path.sep,
275 utils.format_remote_path(host,
276 os.path.dirname(os.path.normpath(dst))),
277 on_execute=on_execute, on_completion=on_completion)
279 @staticmethod
280 def _synchronize_object(src, host, dst, on_execute, on_completion):
281 """Creates a file or empty directory on a remote host.
283 :param src: Empty directory used for rsync purposes
284 :param host: Remote host
285 :param dst: Destination path
286 :param on_execute: Callback method to store pid of process in cache
287 :param on_completion: Callback method to remove pid of process from
288 cache
289 """
291 # For creating path on the remote host rsync --relative path must
292 # be used. With a modern rsync on the sending side (beginning with
293 # 2.6.7), you can insert a dot and a slash into the source path,
294 # like this:
295 # rsync -avR /foo/./bar/baz.c remote:/tmp/
296 # That would create /tmp/bar/baz.c on the remote machine.
297 # (Note that the dot must be followed by a slash, so "/foo/."
298 # would not be abbreviated.)
299 relative_tmp_file_path = os.path.join(
300 src, './',
301 os.path.normpath(dst).strip(os.path.sep))
303 # Do relative rsync local directory with remote root directory
304 processutils.execute(
305 'rsync', '--archive', '--relative', '--no-implied-dirs',
306 relative_tmp_file_path,
307 utils.format_remote_path(host, os.path.sep),
308 on_execute=on_execute, on_completion=on_completion)
310 def copy_file(self, src, dst, on_execute, on_completion, compression):
311 # As far as ploop disks are in fact directories we add '-r' argument
312 args = ['rsync', '-r', '--sparse', src, dst]
313 if compression:
314 args.append('--compress')
315 processutils.execute(
316 *args, on_execute=on_execute, on_completion=on_completion)