Commit a07e4181 authored by Yue Wang's avatar Yue Wang
Browse files

Fix the throughput regression due to read_ahead configuration change on Linux...

Fix the throughput regression due to read_ahead configuration change on Linux distribution with kernel version 5.4.x and above
parent 49012c08
......@@ -210,6 +210,25 @@ man mount.efs
or refer to the [documentation](https://docs.aws.amazon.com/efs/latest/ug/using-amazon-efs-utils.html).
### MacOS
For EC2 instances using Mac distribution, the recommended default options will perform a tls mount:
```
$ sudo mount -t efs file-system-id efs-mount-point/
```
or
```
$ sudo mount -t efs -o tls file-system-id efs-mount-point/
```
To mount without TLS, simply add the `notls` option:
```
$ sudo mount -t efs -o notls file-system-id efs-mount-point/
```
### amazon-efs-mount-watchdog
`efs-utils` contains a watchdog process to monitor the health of TLS mounts. This process is managed by either `upstart` or `systemd` depending on your Linux distribution and `launchd` on Mac distribution, and is started automatically the first time an EFS file system is mounted over TLS.
......@@ -314,6 +333,36 @@ configured on your instance.
After completing the three prerequisite steps, you will be able to see mount status notifications in CloudWatch Logs.
## Optimize readahead max window size on Linux 5.4+
A change in the Linux kernel 5.4+ results a throughput regression on NFS client. With [patch](https://www.spinics.net/lists/linux-nfs/msg75018.html), starting from 5.4.\*, Kernels containing this patch now set the default read_ahead_kb size to 128 KB instead of the previous 15 MB. This read_ahead_kb is used by the Linux kernel to optimize performance on NFS read requests by defining the maximum amount of data an NFS client can pre-fetch in a read call. With the reduced value, an NFS client has to make more read calls to the file system, resulting in reduced performance.
To avoid above throughput regression, efs-utils will modify read_ahead_kb to 15 \* rsize (could be configured via mount option, 1MB by default) after mount success on Linux 5.4+. (not support on MacOS)
This optimization will be enabled by default. To disable this optimization:
```bash
sed -i "s/optimize_readahead = false/optimize_readahead = true/" /etc/amazon/efs/efs-utils.conf
```
To re-enable this optimization
```bash
sed -i "s/optimize_readahead = true/optimize_readahead = false/" /etc/amazon/efs/efs-utils.conf
```
You can mount file system with a given rsize, run:
```bash
$ sudo mount -t efs -o rsize=rsize-value-in-bytes file-system-id efs-mount-point/
```
You can also manually chose a value of read_ahead_kb to optimize read throughput on Linux 5.4+ after mount.
```bash
$ sudo bash -c "echo read-ahead-value-in-kb > /sys/class/bdi/0:$(stat -c '%d' efs-mount-point)/read_ahead_kb"
```
## License Summary
This code is made available under the MIT license.
......
......@@ -31,7 +31,7 @@
%endif
Name : amazon-efs-utils
Version : 1.30.1
Version : 1.30.2
Release : 1%{platform}
Summary : This package provides utilities for simplifying the use of EFS file systems
......@@ -131,6 +131,9 @@ fi
%clean
%changelog
* Thu Apr 15 2021 Yue Wang <wangnyue@amazon.com> - 1.30.2
- Fix the throughput regression due to read_ahead configuration change on Linux distribution with kernel version 5.4.x and above
* Mon Mar 22 2021 Yuan Gao <ygaochn@amazon.com> - 1.30.1
- Support new option: az, enable mount file system to specific availability zone mount target
- Merge PR #84 on Github. Fix to use regional AWS STS endpoints instead of the global endpoint to reduce latency
......
......@@ -11,7 +11,7 @@ set -ex
BASE_DIR=$(pwd)
BUILD_ROOT=${BASE_DIR}/build/debbuild
VERSION=1.30.1
VERSION=1.30.2
RELEASE=1
DEB_SYSTEM_RELEASE_PATH=/etc/os-release
......
......@@ -7,5 +7,5 @@
#
[global]
version=1.30.1
version=1.30.2
release=1
Package: amazon-efs-utils
Architecture: all
Version: 1.30.1
Version: 1.30.2
Section: utils
Depends: python3, nfs-common, stunnel4 (>= 4.56), openssl (>= 1.0.2), util-linux
Priority: optional
......
......@@ -33,6 +33,10 @@ stunnel_check_cert_validity = false
port_range_lower_bound = 20049
port_range_upper_bound = 20449
# Optimize read_ahead_kb for Linux 5.4+
optimize_readahead = true
[mount.cn-north-1]
dns_name_suffix = amazonaws.com.cn
......
......@@ -39,6 +39,7 @@ hard
timeo=600
retrans=2
noresvport
tls (for Mac distributions)
.fi
.if n \{\
.RE
......@@ -70,6 +71,10 @@ options:
\fBtls\fR
Mounts the EFS file system over TLS\&.
.TP
\fBnotls\fR
Mounts the EFS file system without TLS. For EC2 instances using Mac distributions, \
the default mount command mounts the EFS file system over TLS.\&.
.TP
\fBtlsport=\fR\fIn\fR
Configure the TLS relay to listen on the specified port\&.
.TP
......
......@@ -78,7 +78,7 @@ except ImportError:
BOTOCORE_PRESENT = False
VERSION = '1.30.1'
VERSION = '1.30.2'
SERVICE = 'elasticfilesystem'
CLONE_NEWNET = 0x40000000
......@@ -92,6 +92,7 @@ CLOUDWATCH_LOG_SECTION = 'cloudwatch-log'
DEFAULT_CLOUDWATCH_LOG_GROUP = '/aws/efs/utils'
DEFAULT_RETENTION_DAYS = 14
DEFAULT_UNKNOWN_VALUE = 'unknown'
DEFAULT_MACOS_VALUE = 'macos'
LOG_DIR = '/var/log/amazon/efs'
LOG_FILE = 'mount.log'
......@@ -186,6 +187,7 @@ EFS_ONLY_OPTIONS = [
'iam',
'netns',
'noocsp',
'notls',
'ocsp',
'tls',
'tlsport',
......@@ -232,6 +234,12 @@ MACOS_BIG_SUR_RELEASE = 'macOS-11'
SKIP_NO_LIBWRAP_RELEASES = [RHEL8_RELEASE_NAME, CENTOS8_RELEASE_NAME, FEDORA_RELEASE_NAME, OPEN_SUSE_LEAP_RELEASE_NAME,
SUSE_RELEASE_NAME, MACOS_BIG_SUR_RELEASE]
# Multiplier for max read ahead buffer size
# Set default as 15 aligning with prior linux kernel 5.4
DEFAULT_NFS_MAX_READAHEAD_MULTIPLIER = 15
NFS_READAHEAD_CONFIG_PATH_FORMAT = '/sys/class/bdi/0:%s/read_ahead_kb'
NFS_READAHEAD_OPTIMIZE_LINUX_KERNEL_MIN_VERSION = [5, 4]
# MacOS does not support the property of Socket SO_BINDTODEVICE in stunnel configuration
SKIP_NO_SO_BINDTODEVICE_RELEASES = [MACOS_BIG_SUR_RELEASE]
......@@ -1126,7 +1134,7 @@ def get_nfs_mount_options(options):
return ','.join(nfs_options)
def mount_nfs(dns_name, path, mountpoint, options):
def mount_nfs(config, dns_name, path, mountpoint, options):
if 'tls' in options:
mount_path = '127.0.0.1:%s' % path
......@@ -1150,6 +1158,9 @@ def mount_nfs(dns_name, path, mountpoint, options):
message = 'Successfully mounted %s at %s' % (dns_name, mountpoint)
logging.info(message)
publish_cloudwatch_log(CLOUDWATCHLOG_AGENT, message)
# only perform readahead optimize after mount succeed
optimize_readahead_window(mountpoint, options, config)
else:
message = 'Failed to mount %s at %s: returncode=%d, stderr="%s"' % (dns_name, mountpoint, proc.returncode, err.strip())
fatal_error(err.strip(), message, proc.returncode)
......@@ -1219,7 +1230,10 @@ def get_client_info(config):
if 0 < len(client_source) <= CLIENT_SOURCE_STR_LEN_LIMIT:
client_info['source'] = client_source
if not client_info.get('source'):
client_info['source'] = DEFAULT_UNKNOWN_VALUE
if check_if_platform_is_mac():
client_info['source'] = DEFAULT_MACOS_VALUE
else:
client_info['source'] = DEFAULT_UNKNOWN_VALUE
client_info['efs_utils_version'] = VERSION
......@@ -1764,7 +1778,7 @@ def mount_tls(config, init_system, dns_name, path, fs_id, mountpoint, options):
t = threading.Thread(target=poll_tunnel_process, args=(tunnel_proc, fs_id, mount_completed))
t.daemon = True
t.start()
mount_nfs(dns_name, path, mountpoint, options)
mount_nfs(config, dns_name, path, mountpoint, options)
mount_completed.set()
t.join()
......@@ -1809,6 +1823,9 @@ def check_options_validity(options):
if 'ocsp' in options and 'noocsp' in options:
fatal_error('The "ocsp" and "noocsp" options are mutually exclusive')
if 'notls' in options:
fatal_error('The "tls" and "notls" options are mutually exclusive')
if 'accesspoint' in options:
if 'tls' not in options:
fatal_error('The "tls" option is required when mounting via "accesspoint"')
......@@ -2164,6 +2181,61 @@ def handle_general_botocore_exceptions(error):
logging.debug('Unexpected error: %s' % error)
# A change in the Linux kernel 5.4+ results a throughput regression on NFS client.
# With patch (https://bugzilla.kernel.org/show_bug.cgi?id=204939), starting from 5.4.*,
# Linux NFS client is using a fixed default value of 128K as read_ahead_kb.
# Before this patch, the read_ahead_kb equation is (NFS_MAX_READAHEAD) 15 * (client configured read size).
# Thus, with EFS recommendation of rsize (1MB) in mount option,
# NFS client might see a throughput drop in kernel 5.4+, especially for sequential read.
# To fix the issue, below function will modify read_ahead_kb to 15 * rsize (1MB by default) after mount.
def optimize_readahead_window(mountpoint, options, config):
if not should_revise_readahead(config):
return
fixed_readahead_kb = int(DEFAULT_NFS_MAX_READAHEAD_MULTIPLIER * int(options['rsize']) / 1024)
try:
# use "stat -c '%d' mountpoint" to get Device number in decimal
mountpoint_dev_num = subprocess.check_output(['stat', '-c', '"%d"', mountpoint], universal_newlines=True)
# modify read_ahead_kb in /sys/class/bdi/0:[Device Number]/read_ahead_kb
subprocess.check_call(
'echo %s > %s' % (fixed_readahead_kb, NFS_READAHEAD_CONFIG_PATH_FORMAT % mountpoint_dev_num.strip().strip('"')),
shell=True)
except subprocess.CalledProcessError as e:
logging.warning('failed to modify read_ahead_kb: %s with error %s' % (fixed_readahead_kb, e))
# Only modify read_ahead_kb iff
# 1. 'optimize_readahead' is set to true in efs-utils config file
# 2. instance platform is linux
# 3. kernel version of instance is 5.4+
def should_revise_readahead(config):
return config.getboolean(CONFIG_SECTION, 'optimize_readahead') and \
platform.system() == 'Linux' and \
get_linux_kernel_version(
len(NFS_READAHEAD_OPTIMIZE_LINUX_KERNEL_MIN_VERSION)) >= NFS_READAHEAD_OPTIMIZE_LINUX_KERNEL_MIN_VERSION
# Parse Linux kernel version from platform.release()
# Failback to 0.0.0... as invalid version
# Examples:
# platform.release() Parsed version with desired_length:2
# RHEL 3.10.0-1160.el7.x86_64 [3, 10]
# AL2 5.4.105-48.177.amzn2.x86_64 [5, 4]
# Ubuntu 5.4.0-1038-aws [5, 4]
# OpenSUSE 5.3.18-24.37-default [5, 3]
def get_linux_kernel_version(desired_length):
version = []
try:
version = [int(v) for v in platform.release().split('-', 1)[0].split('.')[:desired_length]]
except ValueError:
logging.warning('Failed to retrieve linux kernel version')
# filling 0 at the end
for i in range(len(version), desired_length):
version.append(0)
return version
def main():
parse_arguments_early_exit()
......@@ -2190,10 +2262,13 @@ def main():
dns_name = get_dns_name(config, fs_id, options)
if check_if_platform_is_mac() and 'notls' not in options:
options['tls'] = None
if 'tls' in options:
mount_tls(config, init_system, dns_name, path, fs_id, mountpoint, options)
else:
mount_nfs(dns_name, path, mountpoint, options)
mount_nfs(config, dns_name, path, mountpoint, options)
if '__main__' == __name__:
......
......@@ -48,7 +48,7 @@ except ImportError:
from urllib import urlencode
VERSION = '1.30.1'
VERSION = '1.30.2'
SERVICE = 'elasticfilesystem'
CONFIG_FILE = '/etc/amazon/efs/efs-utils.conf'
......@@ -56,6 +56,7 @@ CONFIG_SECTION = 'mount-watchdog'
CLIENT_INFO_SECTION = 'client-info'
CLIENT_SOURCE_STR_LEN_LIMIT = 100
DEFAULT_UNKNOWN_VALUE = 'unknown'
DEFAULT_MACOS_VALUE = 'macos'
LOG_DIR = '/var/log/amazon/efs'
LOG_FILE = 'mount-watchdog.log'
......@@ -717,7 +718,10 @@ def get_client_info(config):
if 0 < len(client_source) <= CLIENT_SOURCE_STR_LEN_LIMIT:
client_info['source'] = client_source
if not client_info.get('source'):
client_info['source'] = DEFAULT_UNKNOWN_VALUE
if check_if_running_on_macos():
client_info['source'] = DEFAULT_MACOS_VALUE
else:
client_info['source'] = DEFAULT_UNKNOWN_VALUE
client_info['efs_utils_version'] = VERSION
......
......@@ -140,6 +140,7 @@ def test_certificate_with_iam_with_ap_id(mocker, tmpdir):
def _test_recreate_certificate_with_valid_client_source_config(mocker, tmpdir, client_source):
mocker.patch('mount_efs.check_if_platform_is_mac', return_value=False if client_source != 'macos' else True)
config = _get_mock_config(client_info={'source': client_source}) if client_source else _get_config()
client_info = mount_efs.get_client_info(config)
pk_path = _get_mock_private_key_path(mocker, tmpdir)
......@@ -164,7 +165,12 @@ def test_recreate_certificate_with_valid_client_source(mocker, tmpdir):
_test_recreate_certificate_with_valid_client_source_config(mocker, tmpdir, 'TEST')
def test_recreate_certificate_with_valid_macos_client_source(mocker, tmpdir):
_test_recreate_certificate_with_valid_client_source_config(mocker, tmpdir, 'macos')
def _test_certificate_with_iam_with_ap_with_invalid_client_source_config(mocker, tmpdir, client_source):
mocker.patch('mount_efs.check_if_platform_is_mac', return_value=False)
config = _get_mock_config(client_info={'source': client_source}) if client_source else _get_config()
client_info = mount_efs.get_client_info(config)
pk_path = _get_mock_private_key_path(mocker, tmpdir)
......
#
# Copyright 2017-2018 Amazon.com, Inc. and its affiliates. All Rights Reserved.
#
# Licensed under the MIT License. See the LICENSE accompanying this file
# for the specific language governing permissions and limitations under
# the License.
#
import mount_efs
DEFAULT_KERNEL_VERSION_NEEDED_LEN = len(mount_efs.NFS_READAHEAD_OPTIMIZE_LINUX_KERNEL_MIN_VERSION)
def test_get_linux_kernel_version_empty(mocker):
mocker.patch('platform.release', return_value='')
assert [0, 0] == mount_efs.get_linux_kernel_version(DEFAULT_KERNEL_VERSION_NEEDED_LEN)
def test_get_linux_kernel_version_only_dash(mocker):
mocker.patch('platform.release', return_value='-')
assert [0, 0] == mount_efs.get_linux_kernel_version(DEFAULT_KERNEL_VERSION_NEEDED_LEN)
def test_get_linux_kernel_version_no_number(mocker):
mocker.patch('platform.release', return_value='test')
assert [0, 0] == mount_efs.get_linux_kernel_version(DEFAULT_KERNEL_VERSION_NEEDED_LEN)
def test_get_linux_kernel_version(mocker):
mocker.patch('platform.release', return_value='3.10.0-1160.el7.x86_64')
assert [3, 10] == mount_efs.get_linux_kernel_version(DEFAULT_KERNEL_VERSION_NEEDED_LEN)
def test_get_linux_kernel_version_only_major_version(mocker):
mocker.patch('platform.release', return_value='3-1160.el7.x86_64')
assert [3, 0] == mount_efs.get_linux_kernel_version(DEFAULT_KERNEL_VERSION_NEEDED_LEN)
def test_get_linux_kernel_version_no_dash(mocker):
mocker.patch('platform.release', return_value='3.10.0')
assert [3, 10] == mount_efs.get_linux_kernel_version(DEFAULT_KERNEL_VERSION_NEEDED_LEN)
\ No newline at end of file
......@@ -34,10 +34,13 @@ def dummy_contextmanager(*args, **kwargs):
def _test_main(mocker, tls=False, root=True, ap_id=None, iam=False, awsprofile=None, ocsp=False, noocsp=False, port=None,
tlsport=None, awscredsuri=None):
tlsport=None, awscredsuri=None, notls=False):
options = {}
if tls:
options['tls'] = None
if notls:
options['notls'] = None
if ap_id is not None:
options['accesspoint'] = ap_id
if iam:
......@@ -64,6 +67,7 @@ def _test_main(mocker, tls=False, root=True, ap_id=None, iam=False, awsprofile=N
get_dns_mock = mocker.patch('mount_efs.get_dns_name')
parse_arguments_mock = mocker.patch('mount_efs.parse_arguments', return_value=('fs-deadbeef', '/', '/mnt', options))
bootstrap_tls_mock = mocker.patch('mount_efs.bootstrap_tls', side_effect=dummy_contextmanager)
if tls:
mocker.patch('mount_efs.verify_tlsport_can_be_connected', return_value=True)
mount_mock = mocker.patch('mount_efs.mount_nfs')
......@@ -214,3 +218,12 @@ def test_main_supported_macos(mocker):
mocker.patch('mount_efs.check_if_platform_is_mac', return_value=True)
mocker.patch('mount_efs.check_if_mac_version_is_supported', return_value=True)
_test_main(mocker, tls=True, tlsport=TLS_PORT)
def test_main_tls_notls_option(mocker):
mocker.patch('mount_efs.check_if_platform_is_mac', return_value=True)
mocker.patch('mount_efs.check_if_mac_version_is_supported', return_value=True)
_test_main(mocker, notls=True)
def test_main_tls_ocsp_and_noocsp_option(mocker, capsys):
expected_err = 'The "tls" and "notls" options are mutually exclusive'
_test_main_assert_error(mocker, capsys, expected_err, tls=True, tlsport=TLS_PORT, notls=True)
\ No newline at end of file
......@@ -51,8 +51,9 @@ def _mock_popen(mocker, returncode=0, stdout='stdout', stderr='stderr'):
def test_mount_nfs(mocker):
mock = _mock_popen(mocker)
optimize_readahead_window_mock = mocker.patch('mount_efs.optimize_readahead_window')
mount_efs.mount_nfs(DNS_NAME, '/', '/mnt', DEFAULT_OPTIONS)
mount_efs.mount_nfs(CONFIG, DNS_NAME, '/', '/mnt', DEFAULT_OPTIONS)
args, _ = mock.call_args
args = args[0]
......@@ -61,14 +62,17 @@ def test_mount_nfs(mocker):
assert DNS_NAME in args[NFS_MOUNT_PATH_IDX]
assert '/mnt' == args[NFS_MOUNT_POINT_IDX]
utils.assert_called_once(optimize_readahead_window_mock)
def test_mount_nfs_tls(mocker):
mock = _mock_popen(mocker)
optimize_readahead_window_mock = mocker.patch('mount_efs.optimize_readahead_window')
options = dict(DEFAULT_OPTIONS)
options['tls'] = None
mount_efs.mount_nfs(DNS_NAME, '/', '/mnt', options)
mount_efs.mount_nfs(CONFIG, DNS_NAME, '/', '/mnt', options)
args, _ = mock.call_args
args = args[0]
......@@ -76,24 +80,30 @@ def test_mount_nfs_tls(mocker):
assert DNS_NAME not in args[NFS_MOUNT_PATH_IDX]
assert '127.0.0.1' in args[NFS_MOUNT_PATH_IDX]
utils.assert_called_once(optimize_readahead_window_mock)
def test_mount_nfs_failure(mocker):
_mock_popen(mocker, returncode=1)
optimize_readahead_window_mock = mocker.patch('mount_efs.optimize_readahead_window')
with pytest.raises(SystemExit) as ex:
mount_efs.mount_nfs(DNS_NAME, '/', '/mnt', DEFAULT_OPTIONS)
mount_efs.mount_nfs(CONFIG, DNS_NAME, '/', '/mnt', DEFAULT_OPTIONS)
assert 0 != ex.value.code
utils.assert_not_called(optimize_readahead_window_mock)
def test_mount_nfs_tls_netns(mocker):
mock = _mock_popen(mocker)
optimize_readahead_window_mock = mocker.patch('mount_efs.optimize_readahead_window')
options = dict(DEFAULT_OPTIONS)
options['tls'] = None
options['netns'] = NETNS
mount_efs.mount_nfs(DNS_NAME, '/', '/mnt', options)
mount_efs.mount_nfs(CONFIG, DNS_NAME, '/', '/mnt', options)
args, _ = mock.call_args
args = args[0]
......@@ -105,12 +115,15 @@ def test_mount_nfs_tls_netns(mocker):
assert '127.0.0.1' in args[NFS_MOUNT_PATH_IDX + NETNS_NFS_OFFSET]
assert '/mnt' in args[NFS_MOUNT_POINT_IDX + NETNS_NFS_OFFSET]
utils.assert_called_once(optimize_readahead_window_mock)
def test_mount_tls_mountpoint_mounted_with_nfs(mocker, capsys):
options = dict(DEFAULT_OPTIONS)
options['tls'] = None
bootstrap_tls_mock = mocker.patch('mount_efs.bootstrap_tls')
optimize_readahead_window_mock = mocker.patch('mount_efs.optimize_readahead_window')
mocker.patch('os.path.ismount', return_value=True)
_mock_popen(mocker, stdout='nfs')
mount_efs.mount_tls(CONFIG, INIT_SYSTEM, DNS_NAME, PATH, FS_ID, MOUNT_POINT, options)
......@@ -118,12 +131,15 @@ def test_mount_tls_mountpoint_mounted_with_nfs(mocker, capsys):
assert 'is already mounted' in out
utils.assert_not_called(bootstrap_tls_mock)
utils.assert_not_called(optimize_readahead_window_mock)
def test_mount_nfs_macos(mocker):
mock = _mock_popen(mocker)
mocker.patch('mount_efs.check_if_platform_is_mac', return_value=True)
optimize_readahead_window_mock = mocker.patch('mount_efs.optimize_readahead_window')
DEFAULT_OPTIONS['nfsvers'] = 4.0
mount_efs.mount_nfs(DNS_NAME, '/', '/mnt', DEFAULT_OPTIONS)
mount_efs.mount_nfs(CONFIG, DNS_NAME, '/', '/mnt', DEFAULT_OPTIONS)
args, _ = mock.call_args
args = args[0]
......@@ -132,18 +148,22 @@ def test_mount_nfs_macos(mocker):
assert DNS_NAME in args[-2]
assert '/mnt' == args[-1]
utils.assert_called_once(optimize_readahead_window_mock)
def test_mount_nfs_tls_macos(mocker):
mock = _mock_popen(mocker)
mocker.patch('mount_efs.check_if_platform_is_mac', return_value=True)
optimize_readahead_window_mock = mocker.patch('mount_efs.optimize_readahead_window')
DEFAULT_OPTIONS['nfsvers'] = 4.0
options = dict(DEFAULT_OPTIONS)
options['tls'] = None
mount_efs.mount_nfs(DNS_NAME, '/', '/mnt', options)
mount_efs.mount_nfs(CONFIG, DNS_NAME, '/', '/mnt', options)
args, _ = mock.call_args
args = args[0]
assert DNS_NAME not in args[NFS_MOUNT_PATH_IDX_MACOS]
assert '127.0.0.1' in args[NFS_MOUNT_PATH_IDX_MACOS]
utils.assert_called_once(optimize_readahead_window_mock)
#
# Copyright 2017-2018 Amazon.com, Inc. and its affiliates. All Rights Reserved.
#
# Licensed under the MIT License. See the LICENSE accompanying this file
# for the specific language governing permissions and limitations under
# the License.
#
import subprocess
import mount_efs
from mock import MagicMock
from .. import utils
try:
import ConfigParser
except ImportError:
from configparser import ConfigParser
MOUNT_POINT = '/mnt'
DEFAULT_OPTIONS = {'nfsvers': 4.1, 'rsize': 1048576, 'wsize': 1048576, 'hard': None, 'timeo': 600, 'retrans': 2, 'tlsport': 3049}
EXPECTED_MOUNT_POINT_DEV_NUMBER = 54
EXPECTED_READAHEAD_KB_PATH = mount_efs.NFS_READAHEAD_CONFIG_PATH_FORMAT % EXPECTED_MOUNT_POINT_DEV_NUMBER
EXPECTED_CALL_FORMAT = 'echo %s > %s'
def _get_new_mock_config(enable_optimize_readahead):
try:
config = ConfigParser.SafeConfigParser()
except AttributeError:
config = ConfigParser()
config.add_section(mount_efs.CONFIG_SECTION)
config.set(mount_efs.CONFIG_SECTION, 'optimize_readahead', str(enable_optimize_readahead))
return config
def _mock_subprocess_call(mocker, throw_exception=False):
def _side_effect_raise_exception(cmd, shell):
raise subprocess.CalledProcessError(1, cmd)
if throw_exception:
return mocker.patch('subprocess.check_call', side_effect=_side_effect_raise_exception)
else:
return mocker.patch('subprocess.check_call')
def _mock_conditions(mocker, system='Linux', kernel_version=mount_efs.NFS_READAHEAD_OPTIMIZE_LINUX_KERNEL_MIN_VERSION):
mocker.patch('platform.system', return_value=system)
mocker.patch('mount_efs.get_linux_kernel_version', return_value=kernel_version)
def _mock_should_revise_readahead(mocker, should_apply):
mocker.patch('mount_efs.should_revise_readahead', return_value=should_apply)
def test_should_revise_readahead_disable_in_config(mocker):
mock_config = _get_new_mock_config(False)
assert False == mount_efs.should_revise_readahead(mock_config)
def test_should_revise_readahead_on_none_linux(mocker):
mock_config = _get_new_mock_config(True)
_mock_conditions(mocker, system='Unknown')
assert False == mount_efs.should_revise_readahead(mock_config)
def test_should_revise_readahead_on_linux_lower_54(mocker):
mock_config = _get_new_mock_config(True)
_mock_conditions(mocker, kernel_version=[5, 3])
assert False == mount_efs.should_revise_readahead(mock_config