Merge pull request #138386 from Yarny0/tsm-client

This commit is contained in:
Sandro 2022-01-18 20:50:28 +01:00 committed by GitHub
commit 5c4fa6964f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 230 additions and 55 deletions

View file

@ -7,7 +7,7 @@ let
inherit (lib.modules) mkDefault mkIf;
inherit (lib.options) literalExpression mkEnableOption mkOption;
inherit (lib.strings) concatStringsSep optionalString toLower;
inherit (lib.types) addCheck attrsOf lines nullOr package path port str strMatching submodule;
inherit (lib.types) addCheck attrsOf lines nonEmptyStr nullOr package path port str strMatching submodule;
# Checks if given list of strings contains unique
# elements when compared without considering case.
@ -35,7 +35,7 @@ let
'';
};
options.server = mkOption {
type = strMatching ".+";
type = nonEmptyStr;
example = "tsmserver.company.com";
description = ''
Host/domain name or IP address of the IBM TSM server.
@ -56,7 +56,7 @@ let
'';
};
options.node = mkOption {
type = strMatching ".+";
type = nonEmptyStr;
example = "MY-TSM-NODE";
description = ''
Target node name on the IBM TSM server.
@ -144,7 +144,7 @@ let
};
config.name = mkDefault name;
# Client system-options file directives are explained here:
# https://www.ibm.com/support/knowledgecenter/SSEQVQ_8.1.8/client/c_opt_usingopts.html
# https://www.ibm.com/docs/en/spectrum-protect/8.1.13?topic=commands-processing-options
config.extraConfig =
mapAttrs (lib.trivial.const mkDefault) (
{

View file

@ -5,7 +5,7 @@ let
inherit (lib.attrsets) hasAttr;
inherit (lib.modules) mkDefault mkIf;
inherit (lib.options) mkEnableOption mkOption;
inherit (lib.types) nullOr strMatching;
inherit (lib.types) nonEmptyStr nullOr;
options.services.tsmBackup = {
enable = mkEnableOption ''
@ -15,7 +15,7 @@ let
<option>programs.tsmClient.enable</option>
'';
command = mkOption {
type = strMatching ".+";
type = nonEmptyStr;
default = "backup";
example = "incr";
description = ''
@ -24,7 +24,7 @@ let
'';
};
servername = mkOption {
type = strMatching ".+";
type = nonEmptyStr;
example = "mainTsmServer";
description = ''
Create a systemd system service
@ -41,7 +41,7 @@ let
'';
};
autoTime = mkOption {
type = nullOr (strMatching ".+");
type = nullOr nonEmptyStr;
default = null;
example = "12:00";
description = ''
@ -87,16 +87,35 @@ in
environment.DSM_LOG = "/var/log/tsm-backup/";
# TSM needs a HOME dir to store certificates.
environment.HOME = "/var/lib/tsm-backup";
# for exit status description see
# https://www.ibm.com/support/knowledgecenter/en/SSEQVQ_8.1.8/client/c_sched_rtncode.html
serviceConfig.SuccessExitStatus = "4 8";
# The `-se` option must come after the command.
# The `-optfile` option suppresses a `dsm.opt`-not-found warning.
serviceConfig.ExecStart =
"${cfgPrg.wrappedPackage}/bin/dsmc ${cfg.command} -se='${cfg.servername}' -optfile=/dev/null";
serviceConfig.LogsDirectory = "tsm-backup";
serviceConfig.StateDirectory = "tsm-backup";
serviceConfig.StateDirectoryMode = "0750";
serviceConfig = {
# for exit status description see
# https://www.ibm.com/docs/en/spectrum-protect/8.1.13?topic=clients-client-return-codes
SuccessExitStatus = "4 8";
# The `-se` option must come after the command.
# The `-optfile` option suppresses a `dsm.opt`-not-found warning.
ExecStart =
"${cfgPrg.wrappedPackage}/bin/dsmc ${cfg.command} -se='${cfg.servername}' -optfile=/dev/null";
LogsDirectory = "tsm-backup";
StateDirectory = "tsm-backup";
StateDirectoryMode = "0750";
# systemd sandboxing
LockPersonality = true;
NoNewPrivileges = true;
PrivateDevices = true;
#PrivateTmp = true; # would break backup of {/var,}/tmp
#PrivateUsers = true; # would block backup of /home/*
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = "read-only";
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "noaccess";
ProtectSystem = "strict";
RestrictNamespaces = true;
RestrictSUIDSGID = true;
};
startAt = mkIf (cfg.autoTime!=null) cfg.autoTime;
};
};

View file

@ -490,6 +490,7 @@ in
trezord = handleTest ./trezord.nix {};
trickster = handleTest ./trickster.nix {};
trilium-server = handleTestOn ["x86_64-linux"] ./trilium-server.nix {};
tsm-client-gui = handleTest ./tsm-client-gui.nix {};
txredisapi = handleTest ./txredisapi.nix {};
tuptime = handleTest ./tuptime.nix {};
turbovnc-headless-server = handleTest ./turbovnc-headless-server.nix {};

View file

@ -0,0 +1,57 @@
# The tsm-client GUI first tries to connect to a server.
# We can't simulate a server, so we just check if
# it reports the correct connection failure error.
# After that the test persuades the GUI
# to show its main application window
# and verifies some configuration information.
import ./make-test-python.nix ({ lib, pkgs, ... }: {
name = "tsm-client";
enableOCR = true;
machine = { pkgs, ... }: {
imports = [ ./common/x11.nix ];
programs.tsmClient = {
enable = true;
package = pkgs.tsm-client-withGui;
defaultServername = "testserver";
servers.testserver = {
# 192.0.0.8 is a "dummy address" according to RFC 7600
server = "192.0.0.8";
node = "SOME-NODE";
passwdDir = "/tmp";
};
};
};
testScript = ''
machine.succeed("which dsmj") # fail early if this is missing
machine.wait_for_x()
machine.execute("DSM_LOG=/tmp dsmj -optfile=/dev/null >&2 &")
# does it report the "TCP/IP connection failure" error code?
machine.wait_for_window("IBM Spectrum Protect")
machine.wait_for_text("ANS2610S")
machine.send_key("esc")
# it asks to continue to restore a local backupset now;
# "yes" (return) leads to the main application window
machine.wait_for_text("backupset")
machine.send_key("ret")
# main window: navigate to "Connection Information"
machine.wait_for_text("Welcome")
machine.send_key("alt-f") # "File" menu
machine.send_key("c") # "Connection Information"
# "Connection Information" dialog box
machine.wait_for_window("Connection Information")
machine.wait_for_text("SOME-NODE")
machine.wait_for_text("${pkgs.tsm-client.passthru.unwrapped.version}")
machine.shutdown()
'';
meta.maintainers = [ lib.maintainers.yarny ];
})

View file

@ -1,15 +1,20 @@
{ lib
, callPackage
, nixosTests
, stdenv
, autoPatchelfHook
, buildEnv
, fetchurl
, makeWrapper
, procps
, autoPatchelfHook
, rpmextract
, openssl
, zlib
# optional packages that enable certain features
, acl ? null # EXT2/EXT3/XFS ACL support
, jdk8 ? null # Java GUI
, lvm2 ? null # LVM image backup and restore functions
, lvm2 # LVM image backup and restore functions (optional)
, acl # EXT2/EXT3/XFS ACL support (optional)
, gnugrep
, procps
, jdk8 # Java GUI (needed for `enableGui`)
, buildEnv
, makeWrapper
, enableGui ? false # enables Java GUI `dsmj`
# path to `dsm.sys` configuration files
, dsmSysCli ? "/etc/tsm-client/cli.dsm.sys"
, dsmSysApi ? "/etc/tsm-client/api.dsm.sys"
@ -18,7 +23,7 @@
# For an explanation of optional packages
# (features provided by them, version limits), see
# https://www-01.ibm.com/support/docview.wss?uid=swg21052223#Version%208.1
# https://www.ibm.com/support/pages/node/660813#Version%208.1
# IBM Tivoli Storage Manager Client uses a system-wide
@ -40,22 +45,33 @@
# point to this derivations `/dsmi_dir` directory symlink.
# Other environment variables might be necessary,
# depending on local configuration or usage; see:
# https://www.ibm.com/support/knowledgecenter/en/SSEQVQ_8.1.8/client/c_cfg_sapiunix.html
# https://www.ibm.com/docs/en/spectrum-protect/8.1.13?topic=solaris-set-api-environment-variables
# The newest version of TSM client should be discoverable
# by going the the `downloadPage` (see `meta` below),
# there to "Client Latest Downloads",
# "IBM Spectrum Protect Client Downloads and READMEs",
# then to "Linux x86_64 Ubuntu client" (as of 2019-07-15).
# The newest version of TSM client should be discoverable by
# going to the `downloadPage` (see `meta` below).
# Find the "Backup-archive client" table on that page.
# Look for "Download Documents" of the latest release.
# Here, two links must be checked:
# * "IBM Spectrum Protect Client ... Downloads and READMEs":
# In the table at the page's bottom,
# check the date of the "Linux x86_64 client"
# * "IBM Spectrum Protect BA client ... interim fix downloads"
# Look for the "Linux x86_64 client" rows
# in the table # at the bottom of each page.
# Follow the "HTTPS" link of the row with the latest date stamp.
# In the directory listing to show up, pick the big `.tar` file.
#
# (as of 2021-12-18)
let
meta = {
homepage = "https://www.ibm.com/us-en/marketplace/data-protection-and-recovery";
downloadPage = "https://www-01.ibm.com/support/docview.wss?uid=swg21239415";
homepage = "https://www.ibm.com/products/data-protection-and-recovery";
downloadPage = "https://www.ibm.com/support/pages/ibm-spectrum-protect-downloads-latest-fix-packs-and-interim-fixes";
platforms = [ "x86_64-linux" ];
mainProgram = "dsmc";
license = lib.licenses.unfree;
maintainers = [ lib.maintainers.yarny ];
description = "IBM Spectrum Protect (Tivoli Storage Manager) CLI and API";
@ -74,34 +90,53 @@ let
'';
};
passthru.tests = {
test-cli = callPackage ./test-cli.nix {};
test-gui = nixosTests.tsm-client-gui;
};
mkSrcUrl = version:
let
major = lib.versions.major version;
minor = lib.versions.minor version;
patch = lib.versions.patch version;
fixup = lib.lists.elemAt (lib.versions.splitVersion version) 3;
in
"https://public.dhe.ibm.com/storage/tivoli-storage-management/${if fixup=="0" then "maintenance" else "patches"}/client/v${major}r${minor}/Linux/LinuxX86/BA/v${major}${minor}${patch}/${version}-TIV-TSMBAC-LinuxX86.tar";
unwrapped = stdenv.mkDerivation rec {
name = "tsm-client-${version}-unwrapped";
version = "8.1.8.0";
version = "8.1.13.3";
src = fetchurl {
url = "ftp://public.dhe.ibm.com/storage/tivoli-storage-management/maintenance/client/v8r1/Linux/LinuxX86_DEB/BA/v818/${version}-TIV-TSMBAC-LinuxX86_DEB.tar";
sha256 = "0c1d0jm0i7qjd314nhj2vj8fs7sncm1x2n4d6dg4049jniyvjhpk";
url = mkSrcUrl version;
sha256 = "1dwczf236drdaf4jcfzz5154vdwvxf5zraxhrhiddl6n80hnvbcd";
};
inherit meta;
inherit meta passthru;
nativeBuildInputs = [
autoPatchelfHook
rpmextract
];
buildInputs = [
openssl
stdenv.cc.cc
zlib
];
runtimeDependencies = [
lvm2
(lib.attrsets.getLib lvm2)
];
sourceRoot = ".";
postUnpack = ''
for debfile in *.deb
do
ar -x "$debfile"
tar --xz --extract --file=data.tar.xz
rm data.tar.xz
done
rpmextract TIVsm-API64.x86_64.rpm
rpmextract TIVsm-APIcit.x86_64.rpm
rpmextract TIVsm-BA.x86_64.rpm
rpmextract TIVsm-BAcit.x86_64.rpm
rpmextract TIVsm-BAhdw.x86_64.rpm
rpmextract TIVsm-JBB.x86_64.rpm
# use globbing so that version updates don't break the build:
rpmextract gskcrypt64-*.linux.x86_64.rpm
rpmextract gskssl64-*.linux.x86_64.rpm
'';
installPhase = ''
@ -113,7 +148,7 @@ let
# Fix relative symlinks after `/usr` was moved up one level
preFixup = ''
for link in $out/lib/* $out/bin/*
for link in $out/lib{,64}/* $out/bin/*
do
target=$(readlink "$link")
if [ "$(cut -b -6 <<< "$target")" != "../../" ]
@ -126,14 +161,19 @@ let
'';
};
binPath = lib.makeBinPath ([ acl gnugrep procps ]
++ lib.optional enableGui jdk8);
in
buildEnv {
name = "tsm-client-${unwrapped.version}";
inherit meta;
passthru = { inherit unwrapped; };
meta = meta // lib.attrsets.optionalAttrs enableGui {
mainProgram = "dsmj";
};
passthru = passthru // { inherit unwrapped; };
paths = [ unwrapped ];
buildInputs = [ makeWrapper ];
nativeBuildInputs = [ makeWrapper ];
pathsToLink = [
"/"
"/bin"
@ -144,7 +184,7 @@ buildEnv {
# to the so-called "installation directories"
# * Add symlinks to the "installation directories"
# that point to the `dsm.sys` configuration files
# * Drop the Java GUI executable unless `jdk` is present
# * Drop the Java GUI executable unless `enableGui` is set
# * Create wrappers for the command-line interface to
# prepare `PATH` and `DSM_DIR` environment variables
postBuild = ''
@ -152,13 +192,13 @@ buildEnv {
ln --symbolic --no-target-directory opt/tivoli/tsm/client/api/bin64 $out/dsmi_dir
ln --symbolic --no-target-directory "${dsmSysCli}" $out/dsm_dir/dsm.sys
ln --symbolic --no-target-directory "${dsmSysApi}" $out/dsmi_dir/dsm.sys
${lib.optionalString (jdk8==null) "rm $out/bin/dsmj"}
${lib.optionalString (!enableGui) "rm $out/bin/dsmj"}
for bin in $out/bin/*
do
target=$(readlink "$bin")
rm "$bin"
makeWrapper "$target" "$bin" \
--prefix PATH : "$out/dsm_dir:${lib.strings.makeBinPath [ procps acl jdk8 ]}" \
--prefix PATH : "$out/dsm_dir:${binPath}" \
--set DSM_DIR $out/dsm_dir
done
'';

View file

@ -0,0 +1,58 @@
{ lib
, writeText
, runCommand
, tsm-client
}:
# Let the client try to connect to a server.
# We can't simulate a server, so there's no more to test.
let
# 192.0.0.8 is a "dummy address" according to RFC 7600
dsmSysCli = writeText "cli.dsm.sys" ''
defaultserver testserver
server testserver
commmethod v6tcpip
tcpserveraddress 192.0.0.8
nodename ARBITRARYNODENAME
'';
tsm-client_ = tsm-client.override { inherit dsmSysCli; };
env.nativeBuildInputs = [ tsm-client_ ];
versionString =
let
inherit (tsm-client_.passthru.unwrapped) version;
major = lib.versions.major version;
minor = lib.versions.minor version;
patch = lib.versions.patch version;
fixup = lib.lists.elemAt (lib.versions.splitVersion version) 3;
in
"Client Version ${major}, Release ${minor}, Level ${patch}.${fixup}";
in
runCommand "${tsm-client.name}-test-cli" env ''
set -o nounset
set -o pipefail
export DSM_LOG=$(mktemp -d ./dsm_log.XXXXXXXXXXX)
{ dsmc -optfile=/dev/null || true; } | tee dsmc-stdout
# does it report the correct version?
grep --fixed-strings '${versionString}' dsmc-stdout
# does it use the provided dsm.sys config file?
# if it does, it states the node's name
grep ARBITRARYNODENAME dsmc-stdout
# does it try (and fail) to connect to the server?
# if it does, it reports the "TCP/IP connection failure" error code
grep ANS1017E dsmc-stdout
grep ANS1017E $DSM_LOG/dsmerror.log
touch $out
''

View file

@ -4927,8 +4927,8 @@ with pkgs;
timeline = callPackage ../applications/office/timeline { };
tsm-client = callPackage ../tools/backup/tsm-client { jdk8 = null; };
tsm-client-withGui = callPackage ../tools/backup/tsm-client { };
tsm-client = callPackage ../tools/backup/tsm-client { };
tsm-client-withGui = callPackage ../tools/backup/tsm-client { enableGui = true; };
tracker = callPackage ../development/libraries/tracker { };