Require at least 1.6Gb of available RAM to install the server

Verify that there is at least 1.6Gb of usable RAM on the system. Swap
is not considered. While swap would allow a user to minimally install
IPA it would not be a great experience.

Using any proc-based method to check for available RAM does not
work in containers unless /proc is re-mounted so use cgroups
instead. This also handles the case if the container has memory
constraints on it (-m).

There are envs which mount 'proc' with enabled hidepid option 1
so don't assume that is readable.

Add a switch to skip this memory test if the user is sure they
know what they are doing.

is_hidepid() contributed by Stanislav Levin <slev@altlinux.org>

https://pagure.io/freeipa/issue/8404

Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
Reviewed-By: Stanislav Levin <slev@altlinux.org>
This commit is contained in:
Rob Crittenden 2020-08-27 15:22:12 -04:00 committed by Alexander Bokovoy
parent 2e4431af70
commit cfad7af35d
5 changed files with 94 additions and 2 deletions

View File

@ -242,6 +242,7 @@ BuildRequires: python3-netaddr >= %{python_netaddr_version}
BuildRequires: python3-pyasn1
BuildRequires: python3-pyasn1-modules
BuildRequires: python3-six
BuildRequires: python3-psutil
#
# Build dependencies for wheel packaging and PyPI upload
@ -444,6 +445,7 @@ Requires: python3-lxml
Requires: python3-pki >= %{pki_version}
Requires: python3-pyasn1 >= 0.3.2-2
Requires: python3-sssdconfig >= %{sssd_version}
Requires: python3-psutil
Requires: rpm-libs
# Indirect dependency: use newer urllib3 with TLS 1.3 PHA support
%if 0%{?rhel}

View File

@ -18,6 +18,7 @@
#
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import logging
@ -28,6 +29,7 @@ import ldif
import os
import re
import fileinput
import psutil
import sys
import tempfile
import shutil
@ -963,6 +965,84 @@ def check_entropy():
logger.debug("Invalid value in %s %s", paths.ENTROPY_AVAIL, e)
def is_hidepid():
"""Determine if /proc is mounted with hidepid=1/2 option"""
try:
os.lstat('/proc/1/stat')
except (FileNotFoundError, PermissionError):
return True
return False
def in_container():
"""Determine if we're running in a container.
virt-what will return the underlying machine information so
isn't usable here.
systemd-detect-virt requires the whole systemd subsystem which
isn't a reasonable require in a container.
"""
if not is_hidepid():
with open('/proc/1/sched', 'r') as sched:
data_sched = sched.readline()
else:
data_sched = []
with open('/proc/self/cgroup', 'r') as cgroup:
data_cgroup = cgroup.readline()
checks = [
data_sched.split()[0] not in ('systemd', 'init',),
data_cgroup.split()[0] in ('libpod'),
os.path.exists('/.dockerenv'),
os.path.exists('/.dockerinit'),
os.getenv('container', None) is not None
]
return any(checks)
def check_available_memory(ca=False):
"""
Raise an exception if there isn't enough memory for IPA to install.
In a container then psutil will most likely return the host memory
and not the container. If in a container use the cgroup values which
also may not be constrained but it's the best approximation.
2GB is the rule-of-thumb minimum but the server is installable with
less.
The CA uses ~150MB in a fresh install.
Use Kb instead of KiB to leave a bit of slush for the OS
"""
minimum_suggested = 1000 * 1000 * 1000 * 1.6
if not ca:
minimum_suggested -= 150 * 1000 * 1000
if in_container():
if os.path.exists(
'/sys/fs/cgroup/memory/memory.limit_in_bytes'
) and os.path.exists('/sys/fs/cgroup/memory/memory.usage_in_bytes'):
with open('/sys/fs/cgroup/memory/memory.limit_in_bytes') as fd:
limit = int(fd.readline())
with open('/sys/fs/cgroup/memory/memory.usage_in_bytes') as fd:
used = int(fd.readline())
available = limit - used
else:
raise ScriptError(
"Unable to determine the amount of available RAM"
)
else:
available = psutil.virtual_memory().available
logger.debug("Available memory is %sB", available)
if available < minimum_suggested:
raise ScriptError(
"Less than the minimum 1.6GB of RAM is available, "
"%.2fGB available" % (available / (1024 * 1024 * 1024))
)
def load_external_cert(files, ca_subject):
"""
Load and verify external CA certificate chain from multiple files.

View File

@ -330,6 +330,12 @@ class ServerInstallInterface(ServerCertificateInstallInterface,
)
dirsrv_config_file = enroll_only(dirsrv_config_file)
skip_mem_check = knob(
None,
description="Skip checking for minimum required memory",
)
skip_mem_check = enroll_only(skip_mem_check)
@dirsrv_config_file.validator
def dirsrv_config_file(self, value):
if not os.path.exists(value):

View File

@ -346,6 +346,8 @@ def install_check(installer):
dirsrv_ca_cert = None
pkinit_ca_cert = None
if not options.skip_mem_check:
installutils.check_available_memory(ca=options.setup_ca)
tasks.check_ipv6_stack_enabled()
tasks.check_selinux_status()
check_ldap_conf()

View File

@ -569,7 +569,9 @@ def check_remote_version(client, local_version):
"the local version ({})".format(remote_version, local_version))
def common_check(no_ntp):
def common_check(no_ntp, skip_mem_check, setup_ca):
if not skip_mem_check:
installutils.check_available_memory(ca=setup_ca)
tasks.check_ipv6_stack_enabled()
tasks.check_selinux_status()
check_ldap_conf()
@ -776,7 +778,7 @@ def promote_check(installer):
installer._top_dir = tempfile.mkdtemp("ipa")
# check selinux status, http and DS ports, NTP conflicting services
common_check(options.no_ntp)
common_check(options.no_ntp, options.skip_mem_check, options.setup_ca)
if options.setup_ca and any([options.dirsrv_cert_files,
options.http_cert_files,