Import mounted context manager from IPA
This pattern is used in a few places and is very helpful for writing custom deploy steps. Change-Id: Ib7518075f3140a8daf06db52317ff10f8572dcc8
This commit is contained in:
parent
0bbba8e62f
commit
ebdf6784cb
@ -21,6 +21,7 @@ partprobe: CommandFilter, partprobe, root
|
|||||||
mkswap: CommandFilter, mkswap, root
|
mkswap: CommandFilter, mkswap, root
|
||||||
mkfs: CommandFilter, mkfs, root
|
mkfs: CommandFilter, mkfs, root
|
||||||
dd: CommandFilter, dd, root
|
dd: CommandFilter, dd, root
|
||||||
|
mount: CommandFilter, mount, root
|
||||||
|
|
||||||
# ironic_lib/disk_partitioner.py
|
# ironic_lib/disk_partitioner.py
|
||||||
fuser: CommandFilter, fuser, root
|
fuser: CommandFilter, fuser, root
|
||||||
|
@ -668,3 +668,68 @@ class GetRouteSourceTestCase(base.IronicLibTestCase):
|
|||||||
|
|
||||||
source = utils.get_route_source('XXX')
|
source = utils.get_route_source('XXX')
|
||||||
self.assertIsNone(source)
|
self.assertIsNone(source)
|
||||||
|
|
||||||
|
|
||||||
|
@mock.patch('shutil.rmtree', autospec=True)
|
||||||
|
@mock.patch.object(utils, 'execute', autospec=True)
|
||||||
|
@mock.patch('tempfile.mkdtemp', autospec=True)
|
||||||
|
class MountedTestCase(base.IronicLibTestCase):
|
||||||
|
|
||||||
|
def test_temporary(self, mock_temp, mock_execute, mock_rmtree):
|
||||||
|
with utils.mounted('/dev/fake') as path:
|
||||||
|
self.assertIs(path, mock_temp.return_value)
|
||||||
|
mock_execute.assert_has_calls([
|
||||||
|
mock.call("mount", '/dev/fake', mock_temp.return_value,
|
||||||
|
run_as_root=True),
|
||||||
|
mock.call("umount", mock_temp.return_value, run_as_root=True),
|
||||||
|
])
|
||||||
|
mock_rmtree.assert_called_once_with(mock_temp.return_value)
|
||||||
|
|
||||||
|
def test_with_dest(self, mock_temp, mock_execute, mock_rmtree):
|
||||||
|
with utils.mounted('/dev/fake', '/mnt/fake') as path:
|
||||||
|
self.assertEqual('/mnt/fake', path)
|
||||||
|
mock_execute.assert_has_calls([
|
||||||
|
mock.call("mount", '/dev/fake', '/mnt/fake', run_as_root=True),
|
||||||
|
mock.call("umount", '/mnt/fake', run_as_root=True),
|
||||||
|
])
|
||||||
|
self.assertFalse(mock_temp.called)
|
||||||
|
self.assertFalse(mock_rmtree.called)
|
||||||
|
|
||||||
|
def test_with_opts(self, mock_temp, mock_execute, mock_rmtree):
|
||||||
|
with utils.mounted('/dev/fake', '/mnt/fake',
|
||||||
|
opts=['ro', 'foo=bar']) as path:
|
||||||
|
self.assertEqual('/mnt/fake', path)
|
||||||
|
mock_execute.assert_has_calls([
|
||||||
|
mock.call("mount", '/dev/fake', '/mnt/fake', '-o', 'ro,foo=bar',
|
||||||
|
run_as_root=True),
|
||||||
|
mock.call("umount", '/mnt/fake', run_as_root=True),
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_with_type(self, mock_temp, mock_execute, mock_rmtree):
|
||||||
|
with utils.mounted('/dev/fake', '/mnt/fake',
|
||||||
|
fs_type='iso9660') as path:
|
||||||
|
self.assertEqual('/mnt/fake', path)
|
||||||
|
mock_execute.assert_has_calls([
|
||||||
|
mock.call("mount", '/dev/fake', '/mnt/fake', '-t', 'iso9660',
|
||||||
|
run_as_root=True),
|
||||||
|
mock.call("umount", '/mnt/fake', run_as_root=True),
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_failed_to_mount(self, mock_temp, mock_execute, mock_rmtree):
|
||||||
|
mock_execute.side_effect = OSError
|
||||||
|
self.assertRaises(OSError, utils.mounted('/dev/fake').__enter__)
|
||||||
|
mock_execute.assert_called_once_with("mount", '/dev/fake',
|
||||||
|
mock_temp.return_value,
|
||||||
|
run_as_root=True)
|
||||||
|
mock_rmtree.assert_called_once_with(mock_temp.return_value)
|
||||||
|
|
||||||
|
def test_failed_to_unmount(self, mock_temp, mock_execute, mock_rmtree):
|
||||||
|
mock_execute.side_effect = [('', ''),
|
||||||
|
processutils.ProcessExecutionError]
|
||||||
|
with utils.mounted('/dev/fake', '/mnt/fake') as path:
|
||||||
|
self.assertEqual('/mnt/fake', path)
|
||||||
|
mock_execute.assert_has_calls([
|
||||||
|
mock.call("mount", '/dev/fake', '/mnt/fake', run_as_root=True),
|
||||||
|
mock.call("umount", '/mnt/fake', run_as_root=True),
|
||||||
|
])
|
||||||
|
self.assertFalse(mock_rmtree.called)
|
||||||
|
@ -18,12 +18,15 @@
|
|||||||
|
|
||||||
"""Utilities and helper functions."""
|
"""Utilities and helper functions."""
|
||||||
|
|
||||||
|
import contextlib
|
||||||
import copy
|
import copy
|
||||||
import errno
|
import errno
|
||||||
import ipaddress
|
import ipaddress
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import shutil
|
||||||
|
import tempfile
|
||||||
from urllib import parse as urlparse
|
from urllib import parse as urlparse
|
||||||
|
|
||||||
from oslo_concurrency import processutils
|
from oslo_concurrency import processutils
|
||||||
@ -585,3 +588,53 @@ def get_route_source(dest, ignore_link_local=True):
|
|||||||
except (IndexError, ValueError):
|
except (IndexError, ValueError):
|
||||||
LOG.debug('No route to host %(dest)s, route record: %(rec)s',
|
LOG.debug('No route to host %(dest)s, route record: %(rec)s',
|
||||||
{'dest': dest, 'rec': out})
|
{'dest': dest, 'rec': out})
|
||||||
|
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def mounted(source, dest=None, opts=None, fs_type=None):
|
||||||
|
"""A context manager for a temporary mount.
|
||||||
|
|
||||||
|
:param source: A device to mount.
|
||||||
|
:param dest: Mount destination. If not specified, a temporary directory
|
||||||
|
will be created and removed afterwards. An existing destination is
|
||||||
|
not removed.
|
||||||
|
:param opts: Mount options (``-o`` argument).
|
||||||
|
:param fs_type: File system type (``-t`` argument).
|
||||||
|
:returns: A generator yielding the destination.
|
||||||
|
"""
|
||||||
|
params = []
|
||||||
|
if opts:
|
||||||
|
params.extend(['-o', ','.join(opts)])
|
||||||
|
if fs_type:
|
||||||
|
params.extend(['-t', fs_type])
|
||||||
|
|
||||||
|
if dest is None:
|
||||||
|
dest = tempfile.mkdtemp()
|
||||||
|
clean_up = True
|
||||||
|
else:
|
||||||
|
clean_up = False
|
||||||
|
|
||||||
|
mounted = False
|
||||||
|
try:
|
||||||
|
execute("mount", source, dest, *params, run_as_root=True)
|
||||||
|
mounted = True
|
||||||
|
yield dest
|
||||||
|
finally:
|
||||||
|
if mounted:
|
||||||
|
try:
|
||||||
|
execute("umount", dest, run_as_root=True)
|
||||||
|
except (EnvironmentError,
|
||||||
|
processutils.ProcessExecutionError) as exc:
|
||||||
|
LOG.warning(
|
||||||
|
'Unable to unmount temporary location %(dest)s: %(err)s',
|
||||||
|
{'dest': dest, 'err': exc})
|
||||||
|
# NOTE(dtantsur): don't try to remove a still mounted location
|
||||||
|
clean_up = False
|
||||||
|
|
||||||
|
if clean_up:
|
||||||
|
try:
|
||||||
|
shutil.rmtree(dest)
|
||||||
|
except EnvironmentError as exc:
|
||||||
|
LOG.warning(
|
||||||
|
'Unable to remove temporary location %(dest)s: %(err)s',
|
||||||
|
{'dest': dest, 'err': exc})
|
||||||
|
Loading…
x
Reference in New Issue
Block a user