feat(sglang-deploy): replace nginx with caddy
Assisted-by: GLM 5.3 Flash
This commit is contained in:
+188
-24
@@ -16,7 +16,11 @@ my $LOG = "$WORK/log/stubs.log";
|
||||
my $SCRIPT = $ENV{SGLANG_RIG_SCRIPT} // "$RIG/../../sglang-deploy.pl";
|
||||
my $UNIT = '/etc/systemd/system/sglang.service';
|
||||
my $ENVFILE = '/etc/sysconfig/sglang';
|
||||
my $NGINX = '/etc/nginx/conf.d/sglang.conf';
|
||||
my $CADDY = '/etc/caddy/Caddyfile.d/sglang.caddyfile';
|
||||
my $CADDY_MAIN = '/etc/caddy/Caddyfile';
|
||||
my $CADDY_UNIT = '/etc/systemd/system/caddy.service';
|
||||
my $CADDY_BIN = '/usr/local/bin/caddy';
|
||||
my $NGINX_LEGACY = '/etc/nginx/conf.d/sglang.conf';
|
||||
my $CERTDIR = '/etc/ssl/sglang';
|
||||
my $STATEDIR = '/opt/sglang';
|
||||
|
||||
@@ -105,22 +109,27 @@ sub mode_of {
|
||||
}
|
||||
|
||||
sub reset_fixture {
|
||||
for my $path ($UNIT, $ENVFILE, $NGINX) {
|
||||
for my $path ($UNIT, $ENVFILE, $CADDY, $CADDY_MAIN, $CADDY_UNIT, $CADDY_BIN,
|
||||
$NGINX_LEGACY) {
|
||||
unlink($path);
|
||||
}
|
||||
remove_tree('/etc/caddy');
|
||||
remove_tree($CERTDIR);
|
||||
remove_tree($STATEDIR);
|
||||
remove_tree($FIX);
|
||||
remove_tree($ST);
|
||||
# Directories a host that ran server-setup.pl has: nginx's configuration drop-in
|
||||
# and systemd's unit directory.
|
||||
# Directories a host that ran server-setup.pl has: the legacy nginx drop-in
|
||||
# directory an earlier release wrote into, and systemd's unit directory.
|
||||
make_dirs($FIX, $ST, "$WORK/log", '/etc/nginx/conf.d', '/etc/systemd/system');
|
||||
my $fh;
|
||||
open($fh, q{>}, $LOG) and close($fh);
|
||||
# Packages a real host or a previous run has already installed.
|
||||
# Packages a real host or a previous run has already installed. nginx stands
|
||||
# for the drop-in the previous release left behind.
|
||||
write_fixture('rpm-installed', "nginx\n");
|
||||
write_fixture('fw-services', "\n");
|
||||
# firewalld is running: server-setup.pl ensures it, and the firewall step needs it.
|
||||
# The handle is declared first: a my inside the open's argument list does not
|
||||
# reach the right-hand operand of the and on this interpreter.
|
||||
my $fw;
|
||||
open($fw, '>', "$ST/active.firewalld") and close($fw);
|
||||
return;
|
||||
@@ -184,8 +193,8 @@ sub reset_log {
|
||||
# so the script's own PATH lookup finds them and nothing of the real system is used.
|
||||
sub prepare_stubs {
|
||||
make_dirs($BIN, $FIX, $ST, "$WORK/log");
|
||||
for my $name (qw(lspci rpm dnf podman systemctl curl openssl nginx
|
||||
firewall-cmd getenforce getsebool setsebool)) {
|
||||
for my $name (qw(lspci rpm dnf podman systemctl curl openssl caddy tar useradd
|
||||
semodule firewall-cmd getenforce getsebool setsebool)) {
|
||||
my $link = "$BIN/$name";
|
||||
# A link left over from a work directory that moved reads as broken to
|
||||
# -e, and its stale target would leave the stubs unreachable: it is
|
||||
@@ -197,6 +206,21 @@ sub prepare_stubs {
|
||||
return;
|
||||
}
|
||||
|
||||
# The caddy package creates its group; the rig creates it the same way, so the
|
||||
# key permission the package makes possible is exercised for real. The group
|
||||
# file is edited directly: the minimal openEuler image carries no groupadd to
|
||||
# call, and a group entry is all the getgrnam in the script needs.
|
||||
sub prepare_group {
|
||||
return if defined getgrnam('caddy');
|
||||
my $existing = slurp_file('/etc/group');
|
||||
my $gid = 995;
|
||||
$gid++ while $existing =~ /^[^:]+:[^:]*:\Q$gid\E:/m;
|
||||
open(my $fh, '>>', '/etc/group') or die "cannot append to /etc/group: $!\n";
|
||||
print {$fh} "caddy:x:$gid:\n";
|
||||
close($fh);
|
||||
return;
|
||||
}
|
||||
|
||||
sub prepare_devices {
|
||||
# The driver creates these on a real host; the rig fakes them (needs --privileged).
|
||||
return if -e q{/dev/kfd};
|
||||
@@ -214,12 +238,15 @@ sub scenario_fresh {
|
||||
check($rc == 0, 'fresh: exit 0');
|
||||
check(-f $UNIT, 'fresh: unit written');
|
||||
check(-f $ENVFILE, 'fresh: environment file written');
|
||||
check(-f $NGINX, 'fresh: nginx configuration written');
|
||||
check(-f $CADDY, 'fresh: caddy drop-in written');
|
||||
check(-f $CADDY_MAIN, 'fresh: main Caddyfile written');
|
||||
check(-f "$CERTDIR/sglang.crt" && -f "$CERTDIR/sglang.key", 'fresh: certificate written');
|
||||
check(-d "$STATEDIR/modelscope", 'fresh: model cache directory created');
|
||||
check(mode_of($ENVFILE) eq '0600', 'fresh: environment file is 0600 (' . mode_of($ENVFILE) . ')');
|
||||
check(mode_of("$CERTDIR/sglang.key") eq '0600', 'fresh: key is 0600');
|
||||
check(mode_of("$CERTDIR/sglang.key") eq '0640', 'fresh: key is 0640 for the caddy group (' . mode_of("$CERTDIR/sglang.key") . ')');
|
||||
check(mode_of("$CERTDIR/sglang.crt") eq '0644', 'fresh: certificate is 0644');
|
||||
check((stat("$CERTDIR/sglang.key"))[5] == getgrnam('caddy'),
|
||||
'fresh: the key belongs to the caddy group');
|
||||
|
||||
my $env = slurp_file($ENVFILE);
|
||||
check_like($env, qr/^SGLANG_API_KEY=([A-Za-z0-9_-]{43})\n$/, 'fresh: generated key, url-safe, 43 chars');
|
||||
@@ -231,19 +258,29 @@ sub scenario_fresh {
|
||||
check_like($unit, qr/--mem-fraction-static 0\.9/, 'fresh: memory fraction default');
|
||||
check_unlike($unit, qr/SGLANG_USE_AITER/, 'fresh: no Radeon variables on an Instinct host');
|
||||
|
||||
my $nginx = slurp_file($NGINX);
|
||||
check_like($nginx, qr|proxy_pass http://\[::1\]:8000;|, 'fresh: nginx proxies to the loopback engine');
|
||||
check_like($nginx, qr|ssl_certificate /etc/ssl/sglang/sglang\.crt;|, 'fresh: nginx uses the sglang certificate');
|
||||
my $caddy = slurp_file($CADDY);
|
||||
check_like($caddy, qr/reverse_proxy \[::1\]:8000 \{/, 'fresh: caddy proxies to the loopback engine');
|
||||
check_like($caddy, qr|tls /etc/ssl/sglang/sglang\.crt /etc/ssl/sglang/sglang\.key|,
|
||||
'fresh: caddy uses the sglang certificate pair');
|
||||
check_like($caddy, qr/flush_interval -1/, 'fresh: streaming is unbuffered');
|
||||
my $main = slurp_file($CADDY_MAIN);
|
||||
check_like($main, qr/^import Caddyfile\.d\/\*\.caddyfile$/m, 'fresh: the main Caddyfile imports the drop-ins');
|
||||
|
||||
check(count_in_log(qr/^podman pull /) == 1, 'fresh: exactly one image pull');
|
||||
check_like(stub_log(), qr/^podman pull docker\.io\/lmsysorg\/sglang:v0\.5\.19-rocm724-mi30x$/m,
|
||||
'fresh: the pulled image is the resolved tag');
|
||||
check_like(stub_log(), qr/^caddy version$/m, 'fresh: caddy is reported from the binary');
|
||||
check_like(stub_log(), qr/^caddy validate --config \/etc\/caddy\/Caddyfile$/m,
|
||||
'fresh: the configuration is validated before the reload');
|
||||
check_like(stub_log(), qr/^systemctl enable --now caddy$/m, 'fresh: caddy enabled and started');
|
||||
check_like(stub_log(), qr/^systemctl reload caddy$/m, 'fresh: caddy reloaded');
|
||||
check(count_in_log(qr/^systemctl enable sglang$/) == 1, 'fresh: service enabled');
|
||||
check(count_in_log(qr/^systemctl start sglang$/) == 1, 'fresh: service started');
|
||||
check_like(stub_log(), qr/^firewall-cmd --permanent --add-service=https$/m, 'fresh: HTTPS opened');
|
||||
check_like($out, qr/Engine image: docker\.io\/lmsysorg\/sglang:v0\.5\.19-rocm724-mi30x/,
|
||||
'fresh: summary names the image');
|
||||
check_like($out, qr/systemd: sglang running on \[::1\]:8000/, 'fresh: summary names the endpoint');
|
||||
check_like($out, qr/Caddy: configured and reloaded/, 'fresh: summary reports caddy');
|
||||
check_like($out, qr/API key \(shown once, store it securely\)/, 'fresh: the generated key is shown once');
|
||||
check_unlike($out, qr/✗/, 'fresh: no failed step');
|
||||
return;
|
||||
@@ -261,6 +298,7 @@ sub scenario_rerun {
|
||||
check_like(stub_log(), qr/^podman image exists /m, 'rerun: the image is checked instead');
|
||||
check(count_in_log(qr/^systemctl enable sglang$/) == 0, 'rerun: no second enable');
|
||||
check(count_in_log(qr/^systemctl start sglang$/) == 0, 'rerun: no second start');
|
||||
check(count_in_log(qr/^systemctl enable --now caddy$/) == 0, 'rerun: no second caddy enable');
|
||||
check(count_in_log(qr/try-restart/) == 0, 'rerun: no restart, the unit did not change');
|
||||
check(count_in_log(qr/^dnf install /) == 0, 'rerun: no second package transaction');
|
||||
check(slurp_file($ENVFILE) eq $key_before, 'rerun: the API key is reused, not regenerated');
|
||||
@@ -282,7 +320,8 @@ sub scenario_dry_run {
|
||||
check($rc == 0, 'dry run: exit 0');
|
||||
check(!-f $UNIT, 'dry run: no unit written');
|
||||
check(!-f $ENVFILE, 'dry run: no environment file written');
|
||||
check(!-f $NGINX, 'dry run: no nginx configuration written');
|
||||
check(!-f $CADDY, 'dry run: no caddy drop-in written');
|
||||
check(!-e '/etc/caddy', 'dry run: no caddy directory created');
|
||||
check(!-d $CERTDIR, 'dry run: no certificate directory');
|
||||
check(count_in_log(qr/^podman pull /) == 0, 'dry run: no pull');
|
||||
check(count_in_log(qr/^systemctl (enable|start) /) == 0, 'dry run: no service change');
|
||||
@@ -302,14 +341,17 @@ sub scenario_uninstall {
|
||||
check($rc == 0, 'uninstall: exit 0');
|
||||
check(!-f $UNIT, 'uninstall: unit removed');
|
||||
check(!-f $ENVFILE, 'uninstall: environment file removed');
|
||||
check(!-f $NGINX, 'uninstall: nginx configuration removed');
|
||||
check(!-f $CADDY, 'uninstall: caddy drop-in removed');
|
||||
check(-f $CADDY_MAIN, 'uninstall: the main Caddyfile is kept');
|
||||
check(!-d $CERTDIR, 'uninstall: certificate directory removed');
|
||||
check(-d $STATEDIR, 'uninstall: model cache kept');
|
||||
check(-e "$ST/image.docker.io_lmsysorg_sglang_v0.5.19-rocm724-mi30x",
|
||||
'uninstall: the image is kept in podman');
|
||||
check_like(stub_log(), qr/^systemctl stop sglang$/m, 'uninstall: service stopped');
|
||||
check_like(stub_log(), qr/^systemctl disable sglang$/m, 'uninstall: service disabled');
|
||||
check_like(stub_log(), qr/^systemctl reload caddy$/m, 'uninstall: caddy reloaded after the drop-in');
|
||||
check_like($out, qr/SGLang service stopped/, 'uninstall: summary reports the stop');
|
||||
check_like($out, qr/caddy drop-in removed/, 'uninstall: summary reports the drop-in');
|
||||
check_like($out, qr/Kept on the system/, 'uninstall: the kept state is listed');
|
||||
check_like($out, qr/ModelScope cache/, 'uninstall: the cache is named as kept');
|
||||
return;
|
||||
@@ -322,6 +364,78 @@ sub scenario_uninstall_twice {
|
||||
check($rc == 0, 'uninstall twice: exit 0');
|
||||
check_like($out, qr/already absent/, 'uninstall twice: idempotent');
|
||||
check(count_in_log(qr/^systemctl stop /) == 0, 'uninstall twice: nothing to stop');
|
||||
check(count_in_log(qr/^systemctl reload caddy$/) == 0,
|
||||
'uninstall twice: no reload without a drop-in');
|
||||
return;
|
||||
}
|
||||
|
||||
# A host deployed by the nginx release carries its drop-in. Both a deploy and
|
||||
# an uninstall remove it, so exactly one proxy owns :443 afterwards.
|
||||
sub scenario_legacy_nginx {
|
||||
reset_fixture();
|
||||
write_fixture('gpu.txt', "03:00.0 VGA compatible controller [0300]: Advanced Micro Devices, Inc. [AMD/ATI] Instinct MI300X OAM [1002:74a1]\n");
|
||||
write_fixture('rocm.txt', "7.2.4\n");
|
||||
open(my $fh, '>', $NGINX_LEGACY) or die "cannot write $NGINX_LEGACY: $!\n";
|
||||
print {$fh} "server {\n listen 443 ssl;\n}\n";
|
||||
close($fh);
|
||||
# The stub state: nginx is running on this host, so the removal reloads it.
|
||||
my $st;
|
||||
open($st, '>', "$ST/active.nginx") and close($st);
|
||||
my ($rc, $out) = run_script('--model', 'ZhipuAI/GLM-5.3');
|
||||
check($rc == 0, 'legacy nginx: exit 0');
|
||||
check(!-f $NGINX_LEGACY, 'legacy nginx: the old drop-in is gone on deploy');
|
||||
check_like(stub_log(), qr/^systemctl reload nginx$/m,
|
||||
'legacy nginx: the running nginx is reloaded');
|
||||
check_like($out, qr/Legacy nginx configuration: removed/, 'legacy nginx: the summary names it');
|
||||
|
||||
# An uninstall on a host the new release never deployed cleans it too.
|
||||
reset_fixture();
|
||||
write_fixture('gpu.txt', "03:00.0 VGA compatible controller [0300]: Advanced Micro Devices, Inc. [AMD/ATI] Instinct MI300X OAM [1002:74a1]\n");
|
||||
write_fixture('rocm.txt', "7.2.4\n");
|
||||
open($fh, '>', $NGINX_LEGACY) or die "cannot write $NGINX_LEGACY: $!\n";
|
||||
print {$fh} "server {\n listen 443 ssl;\n}\n";
|
||||
close($fh);
|
||||
reset_log();
|
||||
($rc, $out) = run_script('--uninstall');
|
||||
check($rc == 0, 'legacy nginx: uninstall exit 0');
|
||||
check(!-f $NGINX_LEGACY, 'legacy nginx: the old drop-in is gone on uninstall');
|
||||
check_like($out, qr/legacy nginx configuration removed/, 'legacy nginx: the uninstall summary names it');
|
||||
return;
|
||||
}
|
||||
|
||||
# Where no repository carries the caddy package, the official release binary
|
||||
# takes its place: download, extract, install, the service user, and the unit
|
||||
# file the package would have carried.
|
||||
sub scenario_caddy_binary {
|
||||
reset_fixture();
|
||||
write_fixture('gpu.txt', "03:00.0 VGA compatible controller [0300]: Advanced Micro Devices, Inc. [AMD/ATI] Instinct MI300X OAM [1002:74a1]\n");
|
||||
write_fixture('rocm.txt', "7.2.4\n");
|
||||
write_fixture('caddy-no-package', "1\n");
|
||||
unlink('/usr/bin/caddy'); # order independence: no package binary, no stub
|
||||
unlink("$BIN/caddy") or die "cannot remove the caddy stub: $!\n";
|
||||
my ($rc, $out) = run_script('--model', 'ZhipuAI/GLM-5.3');
|
||||
|
||||
check($rc == 0, 'caddy binary: exit 0');
|
||||
check_like(stub_log(), qr/^dnf install -y caddy$/m, 'caddy binary: the package install was attempted');
|
||||
check_like(stub_log(), qr{^curl -fsSL -o \S+ https://github\.com/caddyserver/caddy/releases/download/v2\.10\.2/caddy_2\.10\.2_linux_amd64\.tar\.gz$}m,
|
||||
'caddy binary: the release asset is downloaded');
|
||||
check_like(stub_log(), qr/^tar -xzf \S+ -C \S+$/m, 'caddy binary: the archive is extracted');
|
||||
check(-x $CADDY_BIN, 'caddy binary: the binary is installed executable');
|
||||
check_like(stub_log(), qr/^useradd --system --home-dir \/var\/lib\/caddy --create-home --shell \/sbin\/nologin caddy$/m,
|
||||
'caddy binary: the service user is created');
|
||||
check(-f $CADDY_UNIT, 'caddy binary: the unit file is written');
|
||||
my $unit = slurp_file($CADDY_UNIT);
|
||||
check_like($unit, qr|ExecStart=/usr/local/bin/caddy run --environ --config /etc/caddy/Caddyfile|,
|
||||
'caddy binary: the unit runs the release binary');
|
||||
check_like(stub_log(), qr/^systemctl daemon-reload$/m, 'caddy binary: systemd reloaded');
|
||||
check_like(stub_log(), qr{^caddy validate --config /etc/caddy/Caddyfile$}m,
|
||||
'caddy binary: the installed binary validates');
|
||||
check_like($out, qr/Caddy: configured and reloaded/, 'caddy binary: the deployment completes');
|
||||
|
||||
unlink($CADDY_BIN);
|
||||
unlink($CADDY_UNIT);
|
||||
unlink("$FIX/caddy-no-package");
|
||||
symlink("$RIG/stub.pl", "$BIN/caddy") or die "cannot restore the caddy stub: $!\n";
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -495,7 +609,7 @@ sub scenario_validation {
|
||||
write_fixture('gpu.txt', "03:00.0 VGA compatible controller [0300]: Advanced Micro Devices, Inc. [AMD/ATI] Instinct MI300X OAM [1002:74a1]\n");
|
||||
my ($rc, $out) = run_script('--model', 'ZhipuAI/GLM-5.3', '--port', '443', '--dry-run');
|
||||
check($rc == 1, 'validation: port 443 refused');
|
||||
check_like($out, qr/must be 1-65535 and not 443/, 'validation: port message');
|
||||
check_like($out, qr/must be an integer 1-65535 and not 443/, 'validation: port message');
|
||||
($rc, $out) = run_script('--model', 'not-a-model-id', '--dry-run');
|
||||
check($rc == 1, 'validation: bad model ID refused');
|
||||
check_like($out, qr/Invalid model ID/, 'validation: model message');
|
||||
@@ -545,7 +659,7 @@ sub scenario_non_root {
|
||||
my $rc = $? >> 8;
|
||||
my $out = slurp_file($out_file);
|
||||
check($rc == 0, 'non-root: --version works without root');
|
||||
check_like($out, qr/^sglang-deploy\.pl 2\.0\.0$/, 'non-root: the version is printed');
|
||||
check_like($out, qr/^sglang-deploy\.pl 2\.1\.0$/, 'non-root: the version is printed');
|
||||
|
||||
my $pid3 = fork();
|
||||
die "cannot fork: $!\n" unless defined $pid3;
|
||||
@@ -583,12 +697,12 @@ sub scenario_dependency_section {
|
||||
reset_fixture();
|
||||
write_fixture('gpu.txt', "03:00.0 VGA compatible controller [0300]: Advanced Micro Devices, Inc. [AMD/ATI] Instinct MI300X OAM [1002:74a1]\n");
|
||||
write_fixture('rocm.txt', "7.2.4\n");
|
||||
write_fixture('rpm-installed', "pciutils\ncurl\nopenssl\npodman\nnginx\n");
|
||||
write_fixture('rpm-installed', "pciutils\ncurl\nopenssl\npodman\ntar\n");
|
||||
reset_log();
|
||||
my ($rc, $out) = run_script('--model', 'ZhipuAI/GLM-5.3', '--dry-run');
|
||||
check($rc == 0, 'deps: exit 0');
|
||||
check(count_in_log(qr/^dnf install /) == 0, 'deps: no transaction when all packages are present');
|
||||
check_like($out, qr/All 4 packages already installed/, 'deps: reported as present');
|
||||
check_like($out, qr/All 5 packages already installed/, 'deps: reported as present');
|
||||
|
||||
reset_fixture();
|
||||
write_fixture('gpu.txt', "03:00.0 VGA compatible controller [0300]: Advanced Micro Devices, Inc. [AMD/ATI] Instinct MI300X OAM [1002:74a1]\n");
|
||||
@@ -596,10 +710,41 @@ sub scenario_dependency_section {
|
||||
write_fixture('rpm-installed', "\n");
|
||||
reset_log();
|
||||
($rc, $out) = run_script('--model', 'ZhipuAI/GLM-5.3');
|
||||
check_like(stub_log(), qr/^dnf install -y pciutils curl openssl podman$/m,
|
||||
'deps: the four packages are installed in one transaction');
|
||||
check_like($out, qr/nginx \(reverse proxy\) is not installed: run server-setup.pl first/,
|
||||
'deps: the missing reverse proxy is warned about');
|
||||
check_like(stub_log(), qr/^dnf install -y pciutils curl openssl podman tar$/m,
|
||||
'deps: the five packages are installed in one transaction');
|
||||
check_like(stub_log(), qr/^caddy version$/m, 'deps: caddy is checked after the transaction');
|
||||
check_unlike($out, qr/run server-setup\.pl first/,
|
||||
'deps: no warning points at server-setup.pl, caddy is installed here');
|
||||
return;
|
||||
}
|
||||
|
||||
# CentOS Stream carries caddy in EPEL, and the repository file installs first,
|
||||
# which is what makes the package transaction below it resolvable.
|
||||
sub scenario_epel {
|
||||
reset_fixture();
|
||||
write_fixture('gpu.txt', "03:00.0 VGA compatible controller [0300]: Advanced Micro Devices, Inc. [AMD/ATI] Instinct MI300X OAM [1002:74a1]\n");
|
||||
write_fixture('rocm.txt', "7.2.4\n");
|
||||
my $real = slurp_file('/etc/os-release');
|
||||
open(my $fh, '>', '/etc/os-release') or die "cannot write /etc/os-release: $!\n";
|
||||
print {$fh} "ID=centos\nVERSION_ID=\"10\"\n";
|
||||
close($fh);
|
||||
unlink('/usr/bin/caddy'); # order independence: no binary, no stub
|
||||
unlink("$BIN/caddy");
|
||||
reset_log();
|
||||
my ($rc, $out) = run_script('--model', 'ZhipuAI/GLM-5.3');
|
||||
open(my $back, '>', '/etc/os-release') or die "cannot restore /etc/os-release: $!\n";
|
||||
print {$back} $real;
|
||||
close($back);
|
||||
|
||||
check($rc == 0, 'epel: exit 0');
|
||||
check_like(stub_log(), qr/^dnf install -y epel-release$/m, 'epel: the repository file installs first');
|
||||
check_like(stub_log(), qr/^dnf install -y caddy$/m, 'epel: the package installs after it');
|
||||
check_like($out, qr/Installing caddy\.\.\. package/, 'epel: the package path is taken');
|
||||
check_like($out, qr/Caddy: configured and reloaded/, 'epel: the deployment completes');
|
||||
|
||||
unlink('/usr/bin/caddy');
|
||||
unlink("$FIX/caddy-no-package");
|
||||
symlink("$RIG/stub.pl", "$BIN/caddy") or die "cannot restore the caddy stub: $!\n";
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -619,7 +764,7 @@ sub scenario_custom_layout {
|
||||
);
|
||||
check($rc == 0, 'custom layout: exit 0');
|
||||
check(-f '/etc/systemd/system/llm.service', 'custom layout: the named unit is written');
|
||||
check(-f '/etc/nginx/conf.d/llm.conf', 'custom layout: the named nginx file is written');
|
||||
check(-f '/etc/caddy/Caddyfile.d/llm.caddyfile', 'custom layout: the named caddy drop-in is written');
|
||||
# The certificate file names follow the program, as they did before, not the
|
||||
# service name; the directory follows --cert-dir.
|
||||
check(-f '/etc/ssl/llm/sglang.crt', 'custom layout: certificates follow the directory');
|
||||
@@ -632,7 +777,7 @@ sub scenario_custom_layout {
|
||||
check_like($unit, qr|--volume /srv/llm/modelscope:/root/\.cache/modelscope:Z|,
|
||||
'custom layout: the cache volume follows the state directory');
|
||||
unlink('/etc/systemd/system/llm.service');
|
||||
unlink('/etc/nginx/conf.d/llm.conf');
|
||||
unlink('/etc/caddy/Caddyfile.d/llm.caddyfile');
|
||||
remove_tree('/etc/ssl/llm');
|
||||
remove_tree('/srv/llm');
|
||||
return;
|
||||
@@ -674,17 +819,32 @@ sub scenario_selinux {
|
||||
check(count_in_log(qr/^setsebool /) == 0, 'selinux: nothing set while permissive');
|
||||
check_like($out, qr/SELinux: permissive \(skipped\)/, 'selinux: reported as skipped');
|
||||
|
||||
# Enforcing with no confined caddy policy: the distributions run caddy
|
||||
# unconfined, so no boolean is touched.
|
||||
reset_fixture();
|
||||
write_fixture('gpu.txt', "03:00.0 VGA compatible controller [0300]: Advanced Micro Devices, Inc. [AMD/ATI] Instinct MI300X OAM [1002:74a1]\n");
|
||||
write_fixture('rocm.txt', "7.2.4\n");
|
||||
$ENV{STUB_SELINUX} = 'Enforcing';
|
||||
reset_log();
|
||||
($rc, $out) = run_script('--model', 'ZhipuAI/GLM-5.3');
|
||||
check(count_in_log(qr/^setsebool /) == 0, 'selinux: no boolean without a confined policy');
|
||||
check_like($out, qr/SELinux: caddy runs unconfined \(nothing to do\)/,
|
||||
'selinux: the unconfined case is stated');
|
||||
|
||||
# Enforcing with a confined caddy policy loaded: the boolean is set.
|
||||
reset_fixture();
|
||||
write_fixture('gpu.txt', "03:00.0 VGA compatible controller [0300]: Advanced Micro Devices, Inc. [AMD/ATI] Instinct MI300X OAM [1002:74a1]\n");
|
||||
write_fixture('rocm.txt', "7.2.4\n");
|
||||
write_fixture('semodule-caddy', "1\n");
|
||||
reset_log();
|
||||
($rc, $out) = run_script('--model', 'ZhipuAI/GLM-5.3');
|
||||
check_like(stub_log(), qr/^semodule -l$/m, 'selinux: the loaded modules are asked for');
|
||||
check_like(stub_log(), qr/^setsebool -P httpd_can_network_connect=1$/m, 'selinux: the boolean is set');
|
||||
check_like($out, qr/SELinux: httpd_can_network_connect on/, 'selinux: reported as on');
|
||||
reset_log();
|
||||
my ($rc2, $out2) = run_script('--model', 'ZhipuAI/GLM-5.3');
|
||||
check(count_in_log(qr/^setsebool /) == 0, 'selinux: nothing set when already on');
|
||||
unlink("$FIX/semodule-caddy");
|
||||
delete $ENV{STUB_SELINUX};
|
||||
return;
|
||||
}
|
||||
@@ -695,6 +855,9 @@ my %scenarios = (
|
||||
dry_run => \&scenario_dry_run,
|
||||
uninstall => \&scenario_uninstall,
|
||||
uninstall_twice => \&scenario_uninstall_twice,
|
||||
legacy_nginx => \&scenario_legacy_nginx,
|
||||
caddy_binary => \&scenario_caddy_binary,
|
||||
epel => \&scenario_epel,
|
||||
radeon => \&scenario_radeon,
|
||||
radeon_dev => \&scenario_radeon_dev,
|
||||
radeon_with_image => \&scenario_radeon_with_image,
|
||||
@@ -715,6 +878,7 @@ my %scenarios = (
|
||||
);
|
||||
|
||||
prepare_stubs();
|
||||
prepare_group();
|
||||
prepare_devices();
|
||||
|
||||
my @wanted = @ARGV ? @ARGV : sort keys %scenarios;
|
||||
|
||||
Regular → Executable
+71
-5
@@ -74,6 +74,12 @@ if ($name eq 'rpm') {
|
||||
|
||||
if ($name eq 'dnf') {
|
||||
my @pkgs = grep { !/^-/ && $_ ne 'install' } @args;
|
||||
# Parenthesised on purpose: a named list operator swallows a trailing &&,
|
||||
# and the unparenthesised form asks the grep about a boolean, not the list.
|
||||
if ((grep { $_ eq 'caddy' } @pkgs) && -f "$FIX/caddy-no-package") {
|
||||
print STDERR "Error: Unable to find a match: caddy\n";
|
||||
exit 1;
|
||||
}
|
||||
my $installed = fixture_text('rpm-installed', '');
|
||||
for my $pkg (@pkgs) {
|
||||
$installed .= "$pkg\n" unless $installed =~ /^\Q$pkg\E$/m;
|
||||
@@ -82,6 +88,12 @@ if ($name eq 'dnf') {
|
||||
print {$fh} $installed;
|
||||
close($fh);
|
||||
}
|
||||
# A package transaction that installs caddy leaves the binary where PATH
|
||||
# finds it, the way the real package does. The grep is parenthesised as
|
||||
# above: a named list operator swallows a trailing &&.
|
||||
if ((grep { $_ eq 'caddy' } @pkgs) && !-e '/usr/bin/caddy') {
|
||||
symlink($0, '/usr/bin/caddy');
|
||||
}
|
||||
print "Installing: @pkgs\n";
|
||||
exit 0;
|
||||
}
|
||||
@@ -138,14 +150,32 @@ if ($name eq 'curl') {
|
||||
print "\n__HTTP__$code\n" if $joined =~ /__HTTP__/;
|
||||
exit 0;
|
||||
}
|
||||
if ($url =~ m{api\.github\.com/repos/caddyserver/caddy}) {
|
||||
print fixture_text('caddy-release.json', qq({"tag_name":"v2.10.2"}\n));
|
||||
exit 0;
|
||||
}
|
||||
if ($url =~ m{api\.github\.com}) {
|
||||
print fixture_text('releases.json', qq({"tag_name":"v0.5.19"}\n));
|
||||
exit 0;
|
||||
}
|
||||
if ($url =~ m{github\.com/caddyserver/caddy/releases/download/}) {
|
||||
my $dest;
|
||||
for my $i (0 .. $#args) {
|
||||
$dest = $args[$i + 1] if $args[$i] eq '-o';
|
||||
}
|
||||
if (defined $dest) {
|
||||
open(my $fh, '>', $dest) or exit 1;
|
||||
print {$fh} "stub caddy release archive\n";
|
||||
close($fh);
|
||||
}
|
||||
exit 0;
|
||||
}
|
||||
if ($url =~ m{hub\.docker\.com}) {
|
||||
if (-f "$FIX/image-missing") {
|
||||
print STDERR "curl: (22) The requested URL returned error: 404\n";
|
||||
exit 22;
|
||||
# A definitive 404 on a tag lookup is what the script reads as
|
||||
# unpublished; a curl-level failure would read as cannot-tell.
|
||||
if ($url =~ m{/tags/[A-Za-z0-9._-]+$} && -f "$FIX/image-missing") {
|
||||
print "404";
|
||||
exit 0;
|
||||
}
|
||||
if ($url =~ /tags\?/) {
|
||||
# A tag listing, newest first: the rig's AMD development build.
|
||||
@@ -181,8 +211,44 @@ if ($name eq 'openssl') {
|
||||
exit 0;
|
||||
}
|
||||
|
||||
if ($name eq 'nginx') {
|
||||
print "nginx: configuration file /etc/nginx/nginx.conf test is successful\n";
|
||||
if ($name eq 'caddy') {
|
||||
my $joined = join(' ', @args);
|
||||
if ($joined =~ /version/) {
|
||||
print "v2.10.2 h1:stub\n";
|
||||
exit 0;
|
||||
}
|
||||
if ($joined =~ /validate/) {
|
||||
print "Valid configuration\n";
|
||||
exit 0;
|
||||
}
|
||||
exit 0;
|
||||
}
|
||||
|
||||
if ($name eq 'tar') {
|
||||
# The release-binary install extracts the archive and installs the binary it
|
||||
# names; the stub lays down a link to this dispatcher, so the installed
|
||||
# stand-in answers and logs like every other stubbed command.
|
||||
my $dest_dir;
|
||||
for my $i (0 .. $#args) {
|
||||
$dest_dir = $args[$i + 1] if $args[$i] eq '-C';
|
||||
}
|
||||
if (defined $dest_dir) {
|
||||
unlink("$dest_dir/caddy");
|
||||
symlink($0, "$dest_dir/caddy");
|
||||
}
|
||||
exit 0;
|
||||
}
|
||||
|
||||
if ($name eq 'useradd') {
|
||||
exit 0;
|
||||
}
|
||||
|
||||
if ($name eq 'semodule') {
|
||||
# A loaded module line looks like "100 caddy\tpp"; the fixture decides
|
||||
# whether this host carries a confined caddy policy.
|
||||
if (-f "$FIX/semodule-caddy") {
|
||||
print "100 caddy\tpp\n";
|
||||
}
|
||||
exit 0;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user