fix(m4): 对齐用量价格与发布契约
This commit is contained in:
1 parent
4ab71ce123
commit
c6b5525cf7
43 files changed
+2359
-824
No files matched your search
@@ -4,6 +4,7 @@ umask 077
|
||||
|
||||
release_version="${RELEASE_VERSION:-v2.0.0-local}"
|
||||
suffix="${RELEASE_SMOKE_SUFFIX:-local-$$}"
|
||||
source_image="${RELEASE_SMOKE_SOURCE_IMAGE:-}"
|
||||
case "${suffix}" in
|
||||
*[!A-Za-z0-9_-]*)
|
||||
printf 'RELEASE_SMOKE_SUFFIX contains unsupported characters\n' >&2
|
||||
@@ -117,10 +118,16 @@ case "${docker_arch}" in
|
||||
;;
|
||||
esac
|
||||
|
||||
docker build \
|
||||
--platform "${platform}" \
|
||||
--build-arg "VERSION=${release_version}" \
|
||||
-t "${image}" .
|
||||
if [[ -n "${source_image}" ]]; then
|
||||
test "${source_image}" != "${image}"
|
||||
docker pull --platform "${platform}" "${source_image}"
|
||||
docker image tag "${source_image}" "${image}"
|
||||
else
|
||||
docker build \
|
||||
--platform "${platform}" \
|
||||
--build-arg "VERSION=${release_version}" \
|
||||
-t "${image}" .
|
||||
fi
|
||||
test "$(docker image inspect -f '{{.Config.User}}' "${image}")" = "10001:10001"
|
||||
|
||||
docker volume create "${volume}" >/dev/null
|
||||
@@ -326,7 +333,9 @@ node -e '
|
||||
const prices=JSON.parse(fs.readFileSync(process.argv[2],"utf8"));
|
||||
const usage=JSON.parse(fs.readFileSync(process.argv[3],"utf8"));
|
||||
if(!groups.data.some(item=>item.name==="Task13 Release Smoke Group")) process.exit(1);
|
||||
if(!prices.data.some(item=>item.pattern==="task13-release-model"&&item.source==="user")) process.exit(1);
|
||||
if(!prices.data.overrides.some(
|
||||
item=>item.pattern==="task13-release-model"&&item.source==="user"
|
||||
)) process.exit(1);
|
||||
if(usage.data.summary.request_count<1||usage.data.summary.total_tokens!==12) process.exit(1);
|
||||
' \
|
||||
"${task_tmp}/groups-second.json" \
|
||||
|
||||
Executable
+26
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
image="${1:?image is required}"
|
||||
output=
|
||||
if output="$(
|
||||
docker buildx imagetools inspect \
|
||||
"${image}" \
|
||||
--format '{{.Manifest.Digest}}' 2>&1
|
||||
)"; then
|
||||
test -n "${output}"
|
||||
printf '%s' "${output}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if grep -Eqi 'manifest unknown|manifest_unknown' <<<"${output}" ||
|
||||
{
|
||||
grep -Fqi -- "${image}" <<<"${output}" &&
|
||||
grep -Eqi 'not found|(^|[^0-9])404([^0-9]|$)' <<<"${output}"
|
||||
}; then
|
||||
printf 'absent'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
printf '%s\n' "${output}" >&2
|
||||
exit 1
|
||||
@@ -0,0 +1,226 @@
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$binary = $env:RELEASE_SMOKE_BINARY
|
||||
$checksumFile = $env:RELEASE_SMOKE_CHECKSUM_FILE
|
||||
$releaseVersion = $env:RELEASE_VERSION
|
||||
$port = if ($env:RELEASE_SMOKE_PORT) { $env:RELEASE_SMOKE_PORT } else { "39113" }
|
||||
foreach ($required in @($binary, $checksumFile, $releaseVersion)) {
|
||||
if ([string]::IsNullOrWhiteSpace($required)) {
|
||||
throw "RELEASE_SMOKE_BINARY, RELEASE_SMOKE_CHECKSUM_FILE, and RELEASE_VERSION are required"
|
||||
}
|
||||
}
|
||||
|
||||
Add-Type -TypeDefinition @'
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
public static class ReleaseNativeProcess
|
||||
{
|
||||
private const uint CREATE_NEW_PROCESS_GROUP = 0x00000200;
|
||||
private const uint CTRL_BREAK_EVENT = 1;
|
||||
private const int ERROR_ACCESS_DENIED = 5;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
private struct STARTUPINFO
|
||||
{
|
||||
public uint cb;
|
||||
public string lpReserved;
|
||||
public string lpDesktop;
|
||||
public string lpTitle;
|
||||
public uint dwX;
|
||||
public uint dwY;
|
||||
public uint dwXSize;
|
||||
public uint dwYSize;
|
||||
public uint dwXCountChars;
|
||||
public uint dwYCountChars;
|
||||
public uint dwFillAttribute;
|
||||
public uint dwFlags;
|
||||
public ushort wShowWindow;
|
||||
public ushort cbReserved2;
|
||||
public IntPtr lpReserved2;
|
||||
public IntPtr hStdInput;
|
||||
public IntPtr hStdOutput;
|
||||
public IntPtr hStdError;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct PROCESS_INFORMATION
|
||||
{
|
||||
public IntPtr hProcess;
|
||||
public IntPtr hThread;
|
||||
public uint dwProcessId;
|
||||
public uint dwThreadId;
|
||||
}
|
||||
|
||||
[DllImport(
|
||||
"kernel32.dll",
|
||||
CharSet = CharSet.Unicode,
|
||||
ExactSpelling = true,
|
||||
SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool CreateProcessW(
|
||||
string applicationName,
|
||||
StringBuilder commandLine,
|
||||
IntPtr processAttributes,
|
||||
IntPtr threadAttributes,
|
||||
[MarshalAs(UnmanagedType.Bool)] bool inheritHandles,
|
||||
uint creationFlags,
|
||||
IntPtr environment,
|
||||
string currentDirectory,
|
||||
ref STARTUPINFO startupInfo,
|
||||
out PROCESS_INFORMATION processInformation);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool GenerateConsoleCtrlEvent(
|
||||
uint ctrlEvent,
|
||||
uint processGroupId);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern uint GetConsoleCP();
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool AllocConsole();
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool CloseHandle(IntPtr handle);
|
||||
|
||||
private static void EnsureConsole()
|
||||
{
|
||||
if (GetConsoleCP() != 0 || AllocConsole())
|
||||
{
|
||||
return;
|
||||
}
|
||||
var error = Marshal.GetLastWin32Error();
|
||||
if (error != ERROR_ACCESS_DENIED || GetConsoleCP() == 0)
|
||||
{
|
||||
throw new Win32Exception(error);
|
||||
}
|
||||
}
|
||||
|
||||
public static Process Start(string path)
|
||||
{
|
||||
EnsureConsole();
|
||||
var startupInfo = new STARTUPINFO {
|
||||
cb = (uint)Marshal.SizeOf<STARTUPINFO>()
|
||||
};
|
||||
var commandLine = new StringBuilder("\"" + path + "\"");
|
||||
PROCESS_INFORMATION processInformation;
|
||||
if (!CreateProcessW(
|
||||
path,
|
||||
commandLine,
|
||||
IntPtr.Zero,
|
||||
IntPtr.Zero,
|
||||
false,
|
||||
CREATE_NEW_PROCESS_GROUP,
|
||||
IntPtr.Zero,
|
||||
null,
|
||||
ref startupInfo,
|
||||
out processInformation))
|
||||
{
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||
}
|
||||
try
|
||||
{
|
||||
return Process.GetProcessById((int)processInformation.dwProcessId);
|
||||
}
|
||||
finally
|
||||
{
|
||||
CloseHandle(processInformation.hThread);
|
||||
CloseHandle(processInformation.hProcess);
|
||||
}
|
||||
}
|
||||
|
||||
public static void SendCtrlBreak(int processGroupId)
|
||||
{
|
||||
if (!GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, (uint)processGroupId))
|
||||
{
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||
}
|
||||
}
|
||||
}
|
||||
'@
|
||||
|
||||
$filename = [System.IO.Path]::GetFileName($binary)
|
||||
$checksumLine = Get-Content $checksumFile |
|
||||
Where-Object { $_ -match "\s$([regex]::Escape($filename))$" }
|
||||
if (-not $checksumLine) { throw "missing checksum" }
|
||||
$expectedHash = ($checksumLine -split "\s+")[0].ToLowerInvariant()
|
||||
$beforeHash = (Get-FileHash -Algorithm SHA256 $binary).Hash.ToLowerInvariant()
|
||||
if ($beforeHash -ne $expectedHash) { throw "checksum mismatch before execution" }
|
||||
|
||||
& $binary help | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) { throw "help failed" }
|
||||
|
||||
$dataDir = Join-Path $env:RUNNER_TEMP "gpt-load-native-smoke-$([guid]::NewGuid())"
|
||||
New-Item -ItemType Directory -Path $dataDir | Out-Null
|
||||
$env:DATA_DIR = $dataDir
|
||||
$env:PORT = $port
|
||||
$process = [ReleaseNativeProcess]::Start($binary)
|
||||
try {
|
||||
$health = $null
|
||||
for ($attempt = 0; $attempt -lt 80; $attempt++) {
|
||||
try {
|
||||
$health = Invoke-RestMethod "http://127.0.0.1:$port/health"
|
||||
break
|
||||
} catch {
|
||||
Start-Sleep -Milliseconds 250
|
||||
}
|
||||
}
|
||||
if ($null -eq $health) { throw "health check timed out" }
|
||||
if ($health.version -ne $releaseVersion) { throw "version mismatch" }
|
||||
|
||||
$authFile = Join-Path $dataDir "auth.key"
|
||||
$encryptionFile = Join-Path $dataDir "encryption.key"
|
||||
$databaseFile = Join-Path $dataDir "gpt-load.db"
|
||||
foreach ($file in @($authFile, $encryptionFile, $databaseFile)) {
|
||||
if (-not (Test-Path $file)) { throw "missing generated asset: $file" }
|
||||
}
|
||||
|
||||
$currentSid = [System.Security.Principal.WindowsIdentity]::GetCurrent().User
|
||||
foreach ($file in @($authFile, $encryptionFile)) {
|
||||
$acl = Get-Acl $file
|
||||
if (-not $acl.AreAccessRulesProtected) { throw "credential DACL inherits rules" }
|
||||
$allowRules = @($acl.Access | Where-Object {
|
||||
$_.AccessControlType -eq [System.Security.AccessControl.AccessControlType]::Allow
|
||||
})
|
||||
if ($allowRules.Count -eq 0) { throw "credential DACL has no allow rule" }
|
||||
foreach ($rule in $allowRules) {
|
||||
$sid = $rule.IdentityReference.Translate(
|
||||
[System.Security.Principal.SecurityIdentifier]
|
||||
)
|
||||
if ($sid.Value -ne $currentSid.Value) {
|
||||
throw "credential DACL allows another principal"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$authKey = (Get-Content $authFile -Raw).Trim()
|
||||
Invoke-WebRequest "http://127.0.0.1:$port/" -UseBasicParsing | Out-Null
|
||||
$headers = @{ Authorization = "Bearer $authKey" }
|
||||
Invoke-RestMethod "http://127.0.0.1:$port/api/usage?range=24h" -Headers $headers | Out-Null
|
||||
Invoke-RestMethod "http://127.0.0.1:$port/api/model-prices" -Headers $headers | Out-Null
|
||||
|
||||
[ReleaseNativeProcess]::SendCtrlBreak($process.Id)
|
||||
if (-not $process.WaitForExit(15000)) {
|
||||
throw "graceful shutdown timed out"
|
||||
}
|
||||
if ($process.ExitCode -ne 0) {
|
||||
throw "graceful shutdown exited with code $($process.ExitCode)"
|
||||
}
|
||||
|
||||
$afterHash = (Get-FileHash -Algorithm SHA256 $binary).Hash.ToLowerInvariant()
|
||||
if ($afterHash -ne $expectedHash) { throw "checksum mismatch after execution" }
|
||||
} finally {
|
||||
# Stop-Process is failure cleanup only; success is proven by CTRL_BREAK and exit code 0.
|
||||
if (-not $process.HasExited) {
|
||||
Stop-Process -Id $process.Id -ErrorAction SilentlyContinue
|
||||
$process.WaitForExit(5000) | Out-Null
|
||||
}
|
||||
Remove-Item -Recurse -Force $dataDir -ErrorAction SilentlyContinue
|
||||
}
|
||||
Executable
+70
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
umask 077
|
||||
|
||||
binary="${RELEASE_SMOKE_BINARY:?RELEASE_SMOKE_BINARY is required}"
|
||||
checksum_file="${RELEASE_SMOKE_CHECKSUM_FILE:?RELEASE_SMOKE_CHECKSUM_FILE is required}"
|
||||
release_version="${RELEASE_VERSION:?RELEASE_VERSION is required}"
|
||||
port="${RELEASE_SMOKE_PORT:-39113}"
|
||||
filename="$(basename "${binary}")"
|
||||
|
||||
expected_hash="$(awk -v name="${filename}" '$2 == name {print $1}' "${checksum_file}")"
|
||||
test -n "${expected_hash}"
|
||||
if command -v sha256sum >/dev/null 2>&1; then
|
||||
before_hash="$(sha256sum "${binary}" | awk '{print $1}')"
|
||||
else
|
||||
before_hash="$(shasum -a 256 "${binary}" | awk '{print $1}')"
|
||||
fi
|
||||
test "${before_hash}" = "${expected_hash}"
|
||||
|
||||
chmod +x "${binary}"
|
||||
"${binary}" help >/dev/null
|
||||
|
||||
data_dir="$(mktemp -d)"
|
||||
log_file="$(mktemp)"
|
||||
pid=
|
||||
cleanup() {
|
||||
if [[ -n "${pid}" ]]; then
|
||||
kill -TERM "${pid}" >/dev/null 2>&1 || true
|
||||
wait "${pid}" >/dev/null 2>&1 || true
|
||||
fi
|
||||
rm -rf "${data_dir}" "${log_file}"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
DATA_DIR="${data_dir}" \
|
||||
PORT="${port}" \
|
||||
"${binary}" >"${log_file}" 2>&1 &
|
||||
pid=$!
|
||||
for _ in $(seq 1 80); do
|
||||
if curl -fsS "http://127.0.0.1:${port}/health" >"${data_dir}/health.json"; then
|
||||
break
|
||||
fi
|
||||
sleep 0.25
|
||||
done
|
||||
|
||||
test -s "${data_dir}/health.json"
|
||||
test -f "${data_dir}/auth.key"
|
||||
test -f "${data_dir}/encryption.key"
|
||||
test -f "${data_dir}/gpt-load.db"
|
||||
auth_key="$(cat "${data_dir}/auth.key")"
|
||||
test -n "${auth_key}"
|
||||
grep -F "\"version\":\"${release_version}\"" "${data_dir}/health.json"
|
||||
curl -fsS "http://127.0.0.1:${port}/" | grep -F '<div id="app"></div>' >/dev/null
|
||||
curl -fsS \
|
||||
-H "Authorization: Bearer ${auth_key}" \
|
||||
"http://127.0.0.1:${port}/api/usage?range=24h" >/dev/null
|
||||
curl -fsS \
|
||||
-H "Authorization: Bearer ${auth_key}" \
|
||||
"http://127.0.0.1:${port}/api/model-prices" >/dev/null
|
||||
|
||||
kill -TERM "${pid}"
|
||||
wait "${pid}"
|
||||
pid=
|
||||
|
||||
if command -v sha256sum >/dev/null 2>&1; then
|
||||
after_hash="$(sha256sum "${binary}" | awk '{print $1}')"
|
||||
else
|
||||
after_hash="$(shasum -a 256 "${binary}" | awk '{print $1}')"
|
||||
fi
|
||||
test "${after_hash}" = "${expected_hash}"
|
||||
Reference in new issue
Block a user