diff --git a/Makefile b/Makefile index 4279021ec..0b53b7d9f 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,7 @@ all $(MAKECMDGOALS): $(DIRS) $(DIRS): ifneq ($(MAKECMDGOALS),archives) ifneq ($(MAKECMDGOALS),rpm) +ifneq ($(MAKECMDGOALS),rpm-dkms) ifneq ($(MAKECMDGOALS),srpm) ifneq ($(MAKECMDGOALS),deb) ifneq ($(MAKECMDGOALS),dsc) @@ -29,6 +30,7 @@ endif endif endif endif +endif archives: @tools/pckgen.sh $(PWD) tar zip @@ -36,6 +38,9 @@ archives: rpm: @tools/pckgen.sh $(PWD) rpm --debug +rpm-dkms: + @tools/pckgen.sh $(PWD) rpm --debug --with-dkms + srpm: @tools/pckgen.sh $(PWD) srpm diff --git a/README.md b/README.md index dfcdf3376..228cac177 100644 --- a/README.md +++ b/README.md @@ -4,14 +4,14 @@ [![License](https://img.shields.io/badge/License-BSD_3--Clause-blue.svg)](LICENSE.md) [![Maintained by Unvertical](https://img.shields.io/badge/maintained%20by-Unvertical-0b5fff)](https://unvertical.com/) -Open CAS accelerates Linux applications by caching active (hot) data to +Open CAS accelerates Linux applications by caching active (hot) data to a local flash device inside servers. Open CAS implements caching at the server level, utilizing local high-performance flash media as the cache drive media inside the application server as close as possible to the CPU, thus reducing storage latency as much as possible. The Open Cache Acceleration Software installs into the GNU/Linux operating system itself, as a kernel module. The nature of the integration provides a -cache solution that is transparent to users and applications, and your +cache solution that is transparent to users and applications, and your existing storage infrastructure. No storage migration effort or application changes are required. @@ -72,14 +72,24 @@ you need to run `./configure` once again - this time it should succeed. Alternatively, you can generate RPM/DEB packages from downloaded sources and install those packages instead. To do so, simply run: -__on RPM based systems:__ +**on RPM based systems:** + ``` make rpm rm -f packages/*debug* dnf install ./packages/open-cas-linux*.rpm ``` -__on DEB based systems:__ +**on RPM based systems (with DKMS):** + +``` +make rpm-dkms +rm -f packages/*debug* +dnf install ./packages/open-cas-linux*.rpm +``` + +**on DEB based systems (with DKMS):** + ``` make deb apt install ./packages/open-cas-linux*.deb @@ -105,9 +115,9 @@ Before running tests make sure you have a platform with at least 2 disks (one fo 1. Go to test directory `cd test/functional`. 1. Install dependencies with command `pip3 install -r requirements.txt`. 1. Create DUT config. See example [here](test/functional/config/example_dut_config.yml). - a) Set disks params. You need at least two disks, of which at least one is an SSD drive. - b) For remote execution uncomment and set the `ip`, `user` and `password` fields. - c) For local execution just leave these fields commented. + a) Set disks params. You need at least two disks, of which at least one is an SSD drive. + b) For remote execution uncomment and set the `ip`, `user` and `password` fields. + c) For local execution just leave these fields commented. 1. Run tests using command `pytest-3 --dut-config=` where `` is path to your config file, for example `pytest-3 --dut-config="config/dut_config.yml"`. ## Contributing diff --git a/tools/pckgen.d/rpm/CAS_NAME.spec b/tools/pckgen.d/rpm/CAS_NAME.spec index f092aad83..3c570545b 100644 --- a/tools/pckgen.d/rpm/CAS_NAME.spec +++ b/tools/pckgen.d/rpm/CAS_NAME.spec @@ -11,16 +11,32 @@ # It contains tags in form of which are substituted with particular # values in the build time. # +# The kernel modules can be packaged in two ways, selected with the 'dkms' +# build conditional: +# +# (default) modules prebuilt for one specific kernel, shipped +# in a -modules_k subpackage. +# The build host needs the matching kernel-devel. +# +# rpmbuild --with dkms the module sources, shipped as a DKMS source tree +# and built on the target at install time. It requires +# DKMS support on the target node. %global __python %{__python3} +# Ship the kernel modules as DKMS sources built on the target instead of +# modules prebuilt for a single kernel. Off by default. Enable with: +# rpmbuild --with dkms (or: ./tools/pckgen.sh rpm --with-dkms) +%bcond_with dkms +%if %{without dkms} %define kver # Following define takes kernel version, cuts everything after (and including) # second hyphen (-), and then cuts the architecture (including 'noarch') part. # It's the only package version variant that is accepted by RPM spec. %define kver_pkg %{expand:%(kpkg="%{kver}"; for i in $(seq 2 $(grep -o '-' <<<$kpkg | grep -c .)); do kpkg="${kpkg%%-*}"; done; kpkg="${kpkg%%.$(uname -m)}"; echo "${kpkg%%.noarch}")} %define kver_filename k%{expand:%(echo "%{kver}" | sed -r "y/-/_/;")} +%endif Name: @@ -38,12 +54,18 @@ BuildRequires: gcc BuildRequires: make BuildRequires: procps BuildRequires: python3 +%if %{without dkms} # Allow using different version of kernel-headers package (some distros requires it). BuildRequires: kernel-headers BuildRequires: = %{kver_pkg} BuildRequires: = %{kver_pkg} BuildRequires: BuildRequires: +%endif +# Required by name-and-version rather than as a package, because either module +# package can satisfy it. Switching between them then keeps the requirement +# satisfied inside the one transaction, instead of the erasure of the outgoing +# one taking these tools with it. Requires: -modules-%{version} Requires: python3 Requires: python3-PyYAML @@ -57,17 +79,41 @@ This package contains tools and utilities for managing CAS and monitor running cache instances. +%if %{without dkms} %package modules_%{kver_filename} Summary: Open Cache Acceleration Software kernel modules Group: System Requires: kmod Provides: -modules-%{version} +# The two ways of shipping the modules are alternatives, not one superseding +# the other, so they are mutually exclusive rather than obsoleting: installing +# either over the other has to be an explicit choice (dnf --allowerasing / dnf +# swap) and not something the solver does on its own during an upgrade. rpm +# applies this in both directions, so naming the DKMS package here is enough - +# the prebuilt package cannot be named, its name carries the kernel version. +Conflicts: -modules %description modules_%{kver_filename} Open Cache Acceleration Software (Open CAS) is an open source project encompassing block caching software libraries, adapters, tools and more. The main goal of this cache acceleration software is to accelerate a backend block device(s) by utilizing a higher performance device(s). This package contains only CAS kernel modules. +%else +%package modules +Summary: Open Cache Acceleration Software kernel modules (DKMS source) +Group: System +BuildArch: noarch +Requires: dkms +Provides: -modules-%{version} +%description modules +Open Cache Acceleration Software (Open CAS) is an open source project +encompassing block caching software libraries, adapters, tools and more. +The main goal of this cache acceleration software is to accelerate a +backend block device(s) by utilizing a higher performance device(s). +This package contains the DKMS source tree for the CAS kernel modules. +The modules are compiled and installed on the target system by DKMS for +the kernels installed there, so no prebuilt kernel modules are shipped here. +%endif %prep @@ -75,14 +121,76 @@ This package contains only CAS kernel modules. %build +%if %{without dkms} export KERNEL_DIR=/lib/modules/%{kver}/build/ ./configure --kernel-dir $KERNEL_DIR +%else +# Only userspace is built here. Kernel modules are built on the target via DKMS. +(cd tools/; ./cas_version_gen.sh build) +make -C utils + +%endif %install rm -rf $RPM_BUILD_ROOT + +%if %{without dkms} /usr/bin/make install_files DESTDIR=$RPM_BUILD_ROOT KERNEL_VERSION=%{kver} +%else +# Install userspace before scrubbing the DKMS source tree below. +(cd casadm; make install_files DESTDIR="$RPM_BUILD_ROOT") +(cd utils; make install_files DESTDIR="$RPM_BUILD_ROOT") + +# Scrub build artifacts so the DKMS tree ships source-only: distsync reverses +# the OCF header sync into modules/, utils clean drops manpages, find strips +# any stray kernel build objects. +make -C modules distsync +make -C utils clean +find modules -type f \( -name '*.ko' -o -name '*.o' -o -name '*.cmd' \ + -o -name '*.mod.c' -o -name '*.mod' -o -name 'modules.order' \ + -o -name 'modules.builtin' \) -delete 2>/dev/null || : +rm -rf modules/.tmp_versions 2>/dev/null || : + +# Regenerate version metadata (no build timestamp) for the DKMS source tree. +(cd tools/; ./cas_version_gen.sh) + +# Install DKMS source tree. +DKMS_TREE=%{name}-modules-%{version} +DKMS_ROOT="$RPM_BUILD_ROOT/usr/src/$DKMS_TREE" +install -d -m 755 "$DKMS_ROOT" "$DKMS_ROOT/.metadata" "$DKMS_ROOT/tools" +cp -a .metadata/* "$DKMS_ROOT/.metadata/" 2>/dev/null || : +cp -a modules "$DKMS_ROOT/" +cp -a ocf "$DKMS_ROOT/" +cp -a utils "$DKMS_ROOT/" +cp -a tools/cas_version_gen.sh tools/helpers.mk "$DKMS_ROOT/tools/" +cp -a configure.d "$DKMS_ROOT/" 2>/dev/null || : +cp -a configure Makefile LICENSE.md version "$DKMS_ROOT/" + +# dkms.conf: // are substituted by +# pckgen.sh; $kernelver is a DKMS var (literal via the quoted heredoc). +# No -j here: dkms prepends "make -j ..." (get_num_cpus), and a later +# bare "-j" would win (last -j = unlimited) and clobber the nproc count. +# DKMS builds for a kernel that is not necessarily the running one, so the +# kernel has to be spelled out for './configure' as well - left to itself it +# probes the running kernel and generates a header for the wrong kernel API, +# which 'make' then refuses to build against. +cat > "$DKMS_ROOT/dkms.conf" <<'EOF' +PACKAGE_NAME="-modules" +PACKAGE_VERSION="" +BUILT_MODULE_NAME[0]="cas_cache" +BUILT_MODULE_LOCATION[0]="modules/cas_cache/" +DEST_MODULE_LOCATION[0]="/" +BUILT_MODULE_NAME[1]="cas_bd" +BUILT_MODULE_LOCATION[1]="modules/cas_bd/" +DEST_MODULE_LOCATION[1]="/" +DKMS_KERNEL_DIR="/lib/modules/$kernelver/build" +PRE_BUILD="./configure --kernel-dir $DKMS_KERNEL_DIR" +MAKE[0]="make -C modules/ KERNEL_VERSION=$kernelver KERNEL_DIR=$DKMS_KERNEL_DIR" +AUTOINSTALL=yes +EOF +%endif %post @@ -104,6 +212,7 @@ if [ $1 -eq 0 ]; then fi +%if %{without dkms} %post modules_%{kver_filename} depmod . /etc/os-release @@ -144,6 +253,72 @@ if [ $1 -eq 0 ]; then fi depmod fi +%else +%posttrans modules +# In %posttrans rather than %post: when this replaces the prebuilt package in +# one transaction, that package is erased between the two, and dkms refuses to +# overwrite a module already installed at the same version - so in %post its +# modules would still be in the way, and the erase would then take away the +# ones dkms declined to replace. +# --rpm_safe_upgrade keeps the add+remove pair safe across RPM upgrades +# (dkms(8); also on %preun remove). || : — dkms add returns 3 on re-add +# (reinstall), not a real failure. +dkms add -m %{name}-modules -v %{version} --rpm_safe_upgrade || : +# Build for the running kernel. No || : — fail visibly if kernel-devel is +# missing (dkms returns 0 for "already installed", so reinstalls still work). +dkms install -m %{name}-modules -v %{version} -k "$(uname -r)" || exit 1 +# Best-effort build for other installed kernels (skip those without +# kernel-devel) so a fallback kernel isn't left without modules. +for kver in $(ls /lib/modules 2>/dev/null | grep -vxF "$(uname -r)"); do + [ -d "/lib/modules/$kver/build" ] || continue + dkms install -m %{name}-modules -v %{version} -k "$kver" || : +done + +# kmod->dkms upgrade orphans the old open-cas-linux-modules_k +# (name varies per kernel, can't Obsoletes). Can't rpm -e inside this +# transaction (rpmdb lock), so a detached worker (setsid) waits for the PM +# (dnf/zypper/yum) to exit, then rpm -e --nodeps --noscripts the orphans +# (--noscripts: old %preun fails on archived .ko; --nodeps: orphan has no +# deps) + deletes stale weak-updates symlinks. Guarded (no-op on fresh). +# Giving up the prebuilt modules is only safe once DKMS has modules of its own +# in place. A failed build above exits this scriptlet before the worker is even +# started, and the worker checks the state again for itself - between the two, +# a build that did not produce anything cannot end up taking the modules the +# system is currently using with it. +if [ -n "$(rpm -qa "open-cas-linux-modules_k*" 2>/dev/null)" ]; then + ( setsid sh -c ' + while pgrep -x dnf >/dev/null 2>&1 || pgrep -x zypper >/dev/null 2>&1 || pgrep -x yum >/dev/null 2>&1; do sleep 3; done + dkms status -m %{name}-modules -v %{version} -k "$(uname -r)" 2>/dev/null | grep -q ": installed" || exit 0 + rpm -e --nodeps --noscripts $(rpm -qa "open-cas-linux-modules_k*") >/dev/null 2>&1 || : + # --noscripts skipped the old %postun (weak-modules --remove-modules), + # so clean up after it: drop its dangling weak-updates/block/opencas + # symlinks and rebuild the module index, which still lists the files + # just removed and would otherwise break modprobe and dracut. + find /lib/modules -type l -path "*/weak-updates/block/opencas/*" -delete 2>/dev/null || : + for kver in $(ls /lib/modules 2>/dev/null); do + [ -e "/lib/modules/$kver/modules.dep" ] || continue + depmod -a "$kver" 2>/dev/null || : + done + ' /dev/null 2>&1 ) & +fi + +%preun modules +# Drop dkms's archived old-kmod .ko (original_module) before `dkms remove` +# so it doesn't restore them as orphans (the old kmod package is gone). +rm -rf /var/lib/dkms/%{name}-modules/original_module 2>/dev/null || : +dkms remove -m %{name}-modules -v %{version} --all --rpm_safe_upgrade || : +# dkms weak-links the built modules into kABI-compatible kernels it did not +# build for (/lib/modules//weak-updates/), and `dkms remove` drops the +# built modules while leaving those symlinks behind. Dangling ones break +# dracut ("installkernel failed in module kernel-modules-extra"), which would +# outlive the package, so clear them and refresh the dependency lists. +find /lib/modules -path "*/weak-updates/*" -name "cas_*.ko*" -xtype l -delete 2>/dev/null || : +for kver in $(ls /lib/modules 2>/dev/null); do + [ -e "/lib/modules/$kver/modules.dep" ] || continue + depmod -a "$kver" 2>/dev/null || : +done + +%endif %files @@ -175,14 +350,22 @@ fi %ghost /usr/lib/opencas/opencas.pyo %ghost /usr/lib/opencas/__pycache__ +%if %{without dkms} %files modules_%{kver_filename} %defattr(644, root, root, 755) %license LICENSE.md /lib/modules/%{kver} +%else +%files modules +%defattr(-, root, root, 755) +/usr/src/%{name}-modules-%{version}/ +%endif %changelog -* Tue Apr 28 2026 Qin Fandong - 26.06-1 +* Wed Aug 05 2026 秦凡东 - 26.09-1 +- Add an option to ship the kernel modules as DKMS sources (--with dkms) +* Tue Apr 28 2026 秦凡东 - 26.06-1 - Add opencas_exporter * Mon Aug 25 2025 Rafal Stefanowski - 25.03-1 * Thu Aug 7 2025 Brian J. Murrell - 25.03-1 diff --git a/tools/pckgen.sh b/tools/pckgen.sh index fcb1527e0..17159c17b 100755 --- a/tools/pckgen.sh +++ b/tools/pckgen.sh @@ -52,6 +52,7 @@ SUBMODULES=( # Unset all variables that may be checked for existence: unset ${!GENERATE_*} ARCHIVE_PREPARED DEBUG FAILED_DEPS KVER KVER_FULL LIST_KERNELS MOCK_CFG\ + WITH_DKMS\ OUTPUT_DIR RPM_BUILT SOURCES_DIR SUBMODULES_MISSING TAR_CREATED @@ -99,6 +100,12 @@ print_help() { echo " -o, --output-dir put all created files in the given directory;" echo " default: 'SOURCES_PATH/packages/'" echo " -d, --debug include debug information and create debug packages" + echo " --with-dkms ship the kernel modules as DKMS sources built on the" + echo " target at install time, instead of modules prebuilt" + echo " for one kernel (RPM only; DEB always uses DKMS);" + echo " the build host then needs no kernel-devel and" + echo " --kernel-version does not apply" + echo " --without-dkms explicitly build modules prebuilt for one kernel" echo " -c, --clean clean all temporary files and folders that" echo " may have been left around if $THIS ended" echo " unexpectedly in the previous run" @@ -442,12 +449,14 @@ rpm_spec_prepare() { echo "--- Debug info will be included and debug packages created as well" sed -i "s//%make_build DEBUG_PACKAGE=1/g" "$RPM_SPECS_DIR/$CAS_NAME.spec" + sed -i "s//make -C casadm DEBUG_PACKAGE=1/g" "$RPM_SPECS_DIR/$CAS_NAME.spec" sed -i "//d" "$RPM_SPECS_DIR/$CAS_NAME.spec" if is_suse; then sed -i "/%prep/i %debug_package\n\n" "$RPM_SPECS_DIR/$CAS_NAME.spec" fi else sed -i "s//%make_build/g" "$RPM_SPECS_DIR/$CAS_NAME.spec" + sed -i "s//make -C casadm/g" "$RPM_SPECS_DIR/$CAS_NAME.spec" sed -i "s//%define debug_package %{nil}/g" "$RPM_SPECS_DIR/$CAS_NAME.spec" fi @@ -530,11 +539,11 @@ generate_rpm() { if [ "$MOCK_CFG" ]; then echo "--- Building binary RPM packages in mock environment '$MOCK_CFG'" - if ! (HOME="$TEMP_DIR"; rpmbuild -bs "$RPM_SPECS_DIR/$CAS_NAME.spec"); then + if ! (HOME="$TEMP_DIR"; rpmbuild -bs ${RPM_BCOND_OPTS[@]} "$RPM_SPECS_DIR/$CAS_NAME.spec"); then error "couldn't create SRPM package for mock" fi - if ! mock -r $MOCK_CFG --no-clean --resultdir "$RPM_RPMS_DIR" "$RPM_SRPMS_DIR/$CAS_FILENAME"-*.src.rpm; then + if ! mock -r $MOCK_CFG --no-clean ${RPM_BCOND_OPTS[@]} --resultdir "$RPM_RPMS_DIR" "$RPM_SRPMS_DIR/$CAS_FILENAME"-*.src.rpm; then error "couldn't create RPM packages in mock environment" fi rm -rf "$RPM_SRPMS_DIR"/*.src.rpm @@ -547,15 +556,15 @@ generate_rpm() { else if [ ! "$GENERATE_SRPM" ] && [ "$GENERATE_RPM" ]; then echo "--- Building binary RPM packages" - (HOME="$TEMP_DIR"; rpmbuild -bb --target "$ARCH" "$RPM_SPECS_DIR/$CAS_NAME.spec") + (HOME="$TEMP_DIR"; rpmbuild -bb --target "$ARCH" ${RPM_BCOND_OPTS[@]} "$RPM_SPECS_DIR/$CAS_NAME.spec") if [ $? -ne 0 ]; then error "couldn't create RPM packages" fi - mv -ft "$OUTPUT_DIR" "$RPM_RPMS_DIR/$ARCH"/* + mv -ft "$OUTPUT_DIR" "$RPM_RPMS_DIR"/*/*.rpm fi if [ "$GENERATE_SRPM" ] && [ ! "$GENERATE_RPM" ]; then echo "--- Building source SRPM package" - (HOME="$TEMP_DIR"; rpmbuild -bs "$RPM_SPECS_DIR/$CAS_NAME.spec") + (HOME="$TEMP_DIR"; rpmbuild -bs ${RPM_BCOND_OPTS[@]} "$RPM_SPECS_DIR/$CAS_NAME.spec") if [ $? -ne 0 ]; then error "couldn't create SRPM package" fi @@ -563,12 +572,12 @@ generate_rpm() { fi if [ "$GENERATE_SRPM" ] && [ "$GENERATE_RPM" ]; then echo "--- Building source and binary RPM packages" - (HOME="$TEMP_DIR"; rpmbuild -ba --target "$ARCH" "$RPM_SPECS_DIR/$CAS_NAME.spec") + (HOME="$TEMP_DIR"; rpmbuild -ba --target "$ARCH" ${RPM_BCOND_OPTS[@]} "$RPM_SPECS_DIR/$CAS_NAME.spec") if [ $? -ne 0 ]; then error "couldn't create RPM packages" fi mv -ft "$OUTPUT_DIR" "$RPM_SRPMS_DIR"/* - mv -ft "$OUTPUT_DIR" "$RPM_RPMS_DIR/$ARCH"/* + mv -ft "$OUTPUT_DIR" "$RPM_RPMS_DIR"/*/*.rpm fi fi @@ -675,6 +684,12 @@ while (( $# )); do --debug|-d) DEBUG="debug" ;; + --with-dkms) + WITH_DKMS="with" + ;; + --without-dkms) + WITH_DKMS="without" + ;; --clean|-c) clean_all exit 0 @@ -721,6 +736,16 @@ VERSION_FILE="$SOURCES_DIR/.metadata/cas_version" # CAS version generator location: CAS_VERSION_GEN="$SOURCES_DIR/tools/cas_version_gen.sh" +# RPM build conditional selecting the DKMS packaging mode (see %bcond_with +# dkms in the spec). --with-dkms/--without-dkms map onto rpmbuild's +# --with/--without; passing neither leaves the spec default (off). +RPM_BCOND_OPTS=() +if [ "$WITH_DKMS" == "with" ]; then + RPM_BCOND_OPTS+=(--with dkms) +elif [ "$WITH_DKMS" == "without" ]; then + RPM_BCOND_OPTS+=(--without dkms) +fi + check_cas_version # CAS naming convention: @@ -750,8 +775,9 @@ echo -e "\n" check_dependencies [ "$MOCK_CFG" ] && mock_prepare [ "$LIST_KERNELS" ] && list_kernels -# Only RPM packages are built for specific kernel version. -[ "$GENERATE_RPM" ] && check_kernel_version +# Only RPM packages with prebuilt modules are built for a specific kernel +# version; with --with-dkms they are built on the target instead. +[ "$GENERATE_RPM" ] && [ "$WITH_DKMS" != "with" ] && check_kernel_version create_dir "$OUTPUT_DIR" for package in ${!GENERATE_*}; do ${package,,}