Files
fission-console/scripts/test_heavy.sh
T

995 lines
38 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# test_heavy.sh — поиск багов: большой код, CPU-нагрузка, большой ответ,
# архивы из ZIP, быстрые серии invoke, параллельные вызовы,
# медленные функции, давление по памяти.
# Все 4 языка: Python, Node.js, Ruby, PHP.
#
# ВАЖНО: Ruby и PHP invoke ранее не тестировались вообще!
# Умеренная нагрузка (кластер небольшой, но нагрузка реальная).
#
# Запуск: bash scripts/test_heavy.sh
# На ВМ: bash ~/terra/fission/scripts/test_heavy.sh
set -uo pipefail
ROOT_DIR=$(cd "$(dirname "$0")" && pwd)
source "${ROOT_DIR}/lib.sh"
RUN_ID=$(mk_run_id)
SUB="livetest@test.local"
TMP=$(mktemp -d)
echo ""
echo "════════════════════════════════════════════════════════"
echo " test_heavy.sh run=${RUN_ID}"
echo "════════════════════════════════════════════════════════"
declare -a ALL_FNS=()
cleanup() {
rm -rf "$TMP" 2>/dev/null || true
for fn in "${ALL_FNS[@]:-}"; do
curl -s -X DELETE "${BASE}/functions/${fn}" \
-H "X-Auth-Token: ${SUB}" >/dev/null 2>&1 || true
done
}
trap cleanup EXIT
# ─── helpers ───────────────────────────────────────────────────────────────
# reg FN — регистрирует имя функции для cleanup
reg() { ALL_FNS+=("$1"); }
# create_json LANG FN CODE_FILE — создаёт функцию из файла с кодом
create_json() {
local lang="$1" fn="$2" codefile="$3"
local code_escaped
code_escaped=$(cat "$codefile" | json_escape)
reg "$fn"
local R
R=$(curl -s -w "\n%{http_code}" -X POST "${BASE}/functions" \
-H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" \
-d "{\"name\":\"${fn}\",\"language\":\"${lang}\",\"code\":${code_escaped}}" 2>/dev/null)
check_http "$(echo "$R" | tail -1)" "201" "${lang^^} ${fn} CREATE → 201"
}
# invoke_once FN EXPECTED LABEL — один invoke, без retry
invoke_once() {
local fn="$1" expected="$2" label="$3"
local resp status raw err
resp=$(curl -s --max-time 60 -X POST "${BASE}/functions/${fn}/invoke" \
-H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" -d '{}')
status=$(json_field "$resp" status)
raw=$(json_field "$resp" response_raw)
err=$(json_field "$resp" error)
echo " fn=$fn status=$status raw=$(printf '%s' "$raw" | head -c 100) err=$(printf '%s' "$err" | head -c 80)"
if [ "$status" = "200" ] && printf '%s' "$raw" | grep -qF "$expected"; then
pass "$label → 200 + '${expected}'"
return 0
else
fail "$label → status=${status:-?} (ожидали 200 + '${expected}')"
return 1
fi
}
# ═══════════════════════════════════════════════════════════════════════════
# SECTION 1: БОЛЬШОЙ КОД — ~100 строк на язык, из inline-кода
# Ищем: лимиты на размер кода, проблемы загрузки, синтаксические баги
# ═══════════════════════════════════════════════════════════════════════════
echo ""
echo "────────────────────────────────────────────────────────"
echo " S1: БОЛЬШОЙ КОД (Python / Node.js / Ruby / PHP)"
echo "────────────────────────────────────────────────────────"
FN_BIG_PY="t-big-py-${RUN_ID}"
cat > "${TMP}/big_py.py" << 'PYEOF'
import math, hashlib, json
CONSTS = {f"k{i}": i * 3.14159265 for i in range(20)}
def fib(n):
a, b = 0, 1
for _ in range(n - 1): a, b = b, a + b
return b
def sieve(limit):
is_p = bytearray([1]) * (limit + 1)
is_p[0] = is_p[1] = 0
for i in range(2, int(limit**0.5) + 1):
if is_p[i]:
for j in range(i*i, limit+1, i): is_p[j] = 0
return sum(is_p)
def rot13(s):
return s.translate(str.maketrans(
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
'nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM'))
def caesar(s, shift=7):
res = []
for c in s:
if 'a' <= c <= 'z': res.append(chr((ord(c) - 97 + shift) % 26 + 97))
elif 'A' <= c <= 'Z': res.append(chr((ord(c) - 65 + shift) % 26 + 65))
else: res.append(c)
return ''.join(res)
def mat_trace(m):
return sum(m[i][i] for i in range(len(m)))
def palindrome(s):
t = s.lower().replace(' ', '')
return t == t[::-1]
def flatten(lst):
out = []
for item in lst:
if isinstance(item, list): out.extend(flatten(item))
else: out.append(item)
return out
def checksum(data):
return hashlib.sha256(data.encode()).hexdigest()[:16]
def running_sum(lst):
total = 0; out = []
for v in lst: total += v; out.append(total)
return out
def count_distinct(lst):
return len(set(lst))
def group_by_mod(lst, n):
g = {i: [] for i in range(n)}
for v in lst: g[v % n].append(v)
return {str(k): len(v) for k, v in g.items()}
def pascal_row(n):
row = [1]
for i in range(1, n + 1): row.append(row[-1] * (n - i + 1) // i)
return row
def deduplicate(lst):
seen = set(); out = []
for v in lst:
if v not in seen: seen.add(v); out.append(v)
return out
def reverse_words(s):
return ' '.join(s.split()[::-1])
def count_vowels(s):
return sum(1 for c in s.lower() if c in 'aeiou')
def is_prime(n):
if n < 2: return False
if n == 2: return True
if n % 2 == 0: return False
for i in range(3, int(math.sqrt(n)) + 1, 2):
if n % i == 0: return False
return True
def main(event, context):
mat = [[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]]
data = list(range(1, 51))
nested = [[1,[2,3]],[4,[5,[6,7]]],8]
result = {
"tag": "big-code-py-ok",
"fib_25": fib(25),
"primes_50": sieve(50),
"rot13": rot13("Hello Fission"),
"caesar": caesar("heavy"),
"const_sum": round(sum(CONSTS.values()), 2),
"mat_trace": mat_trace(mat),
"palindrome": palindrome("racecar"),
"distinct": count_distinct([1,2,2,3,3,3,4]),
"flat_len": len(flatten(nested)),
"checksum": checksum("fission-heavy-test"),
"rsum_last": running_sum(data)[-1],
"pascal_4": pascal_row(4),
"dedup_len": len(deduplicate([1,2,2,3,3,3,4,5,5])),
"rev_words": reverse_words("big code python test"),
"vowels": count_vowels("fission heavy test"),
"is_prime_97": is_prime(97),
"groups_3": group_by_mod(data[:30], 3),
}
return json.dumps(result), 200
PYEOF
create_json "python" "$FN_BIG_PY" "${TMP}/big_py.py"
FN_BIG_NODE="t-big-node-${RUN_ID}"
cat > "${TMP}/big_node.js" << 'JSEOF'
const crypto = require('crypto');
const CONSTS = {};
for (let i = 0; i < 20; i++) CONSTS[`k${i}`] = i * Math.PI;
function fib(n) {
let a = 0, b = 1;
for (let i = 1; i < n; i++) { let t = a + b; a = b; b = t; }
return b;
}
function sieve(limit) {
const isP = new Uint8Array(limit + 1).fill(1);
isP[0] = isP[1] = 0;
for (let i = 2; i * i <= limit; i++)
if (isP[i]) for (let j = i*i; j <= limit; j += i) isP[j] = 0;
return isP.reduce((s, v) => s + v, 0);
}
function rot13(s) {
return s.replace(/[a-zA-Z]/g, c => {
const b = c <= 'Z' ? 65 : 97;
return String.fromCharCode((c.charCodeAt(0) - b + 13) % 26 + b);
});
}
function caesar(s, shift = 7) {
return s.replace(/[a-zA-Z]/g, c => {
const b = c <= 'Z' ? 65 : 97;
return String.fromCharCode((c.charCodeAt(0) - b + shift) % 26 + b);
});
}
function matTrace(m) { return m.reduce((s, row, i) => s + row[i], 0); }
function palindrome(s) { const t = s.toLowerCase().replace(/ /g, ''); return t === t.split('').reverse().join(''); }
function flatten(arr) { return arr.reduce((a, v) => a.concat(Array.isArray(v) ? flatten(v) : v), []); }
function checksum(d) { return crypto.createHash('sha256').update(d).digest('hex').slice(0, 16); }
function runningSum(arr) { let t = 0; return arr.map(v => (t += v, t)); }
function countDistinct(arr) { return new Set(arr).size; }
function deduplicate(arr) { const s = new Set(); return arr.filter(v => !s.has(v) && s.add(v)); }
function groupByMod(arr, n) { const g = {}; for (let i = 0; i < n; i++) g[i] = 0; arr.forEach(v => g[v % n]++); return g; }
function pascalRow(n) { const r = [1]; for (let i = 1; i <= n; i++) r.push((r[i-1] * (n-i+1) / i) | 0); return r; }
function reverseWords(s) { return s.split(' ').reverse().join(' '); }
function countVowels(s) { return (s.toLowerCase().match(/[aeiou]/g) || []).length; }
function isPrime(n) {
if (n < 2) return false;
for (let i = 2; i * i <= n; i++) if (n % i === 0) return false;
return true;
}
const mat = [[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]];
const nested = [[1,[2,3]],[4,[5,[6,7]]],8];
module.exports = async function(ctx) {
const data = Array.from({length: 50}, (_, i) => i + 1);
const result = {
tag: "big-code-node-ok",
fib_25: fib(25),
primes_50: sieve(50),
rot13: rot13("Hello Fission"),
caesar: caesar("heavy"),
const_sum: Math.round(Object.values(CONSTS).reduce((a,b) => a+b, 0) * 100) / 100,
mat_trace: matTrace(mat),
palindrome: palindrome("racecar"),
distinct: countDistinct([1,2,2,3,3,3,4]),
flat_len: flatten(nested).length,
checksum: checksum("fission-heavy-test"),
rsum_last: runningSum(data).at(-1),
pascal_4: pascalRow(4),
dedup_len: deduplicate([1,2,2,3,3,3,4,5,5]).length,
rev_words: reverseWords("big code node test"),
vowels: countVowels("fission heavy test"),
is_prime_97: isPrime(97),
groups_3: groupByMod(data.slice(0,30), 3),
};
return { status: 200, body: JSON.stringify(result) };
};
JSEOF
create_json "nodejs" "$FN_BIG_NODE" "${TMP}/big_node.js"
FN_BIG_RUBY="t-big-ruby-${RUN_ID}"
cat > "${TMP}/big_ruby.rb" << 'RBEOF'
require 'json'
require 'digest'
CONSTS = (0...20).map { |i| [:"k#{i}", i * Math::PI] }.to_h
def fib(n)
a, b = 0, 1
(n - 1).times { a, b = b, a + b }
b
end
def sieve(limit)
is_p = Array.new(limit + 1, true)
is_p[0] = is_p[1] = false
(2..Math.sqrt(limit).to_i).each do |i|
next unless is_p[i]
(i*i..limit).step(i) { |j| is_p[j] = false }
end
is_p.count(true)
end
def rot13(s)
s.tr('a-zA-Z', 'n-za-mN-ZA-M')
end
def caesar(s, shift = 7)
s.chars.map do |c|
if c =~ /[a-z]/ then ((c.ord - 97 + shift) % 26 + 97).chr
elsif c =~ /[A-Z]/ then ((c.ord - 65 + shift) % 26 + 65).chr
else c
end
end.join
end
def mat_trace(m)
m.each_with_index.sum { |row, i| row[i] }
end
def palindrome?(s)
t = s.downcase.delete(' ')
t == t.reverse
end
def flatten_nested(lst)
lst.each_with_object([]) do |item, out|
item.is_a?(Array) ? out.concat(flatten_nested(item)) : out << item
end
end
def checksum(data)
Digest::SHA256.hexdigest(data)[0, 16]
end
def running_sum(arr)
total = 0
arr.map { |v| total += v; total }
end
def count_distinct(arr)
arr.uniq.length
end
def group_by_mod(arr, n)
(0...n).each_with_object({}) { |i, h| h[i.to_s] = arr.count { |v| v % n == i } }
end
def pascal_row(n)
row = [1]
(1..n).each { |i| row << row[-1] * (n - i + 1) / i }
row
end
def reverse_words(s)
s.split.reverse.join(' ')
end
def count_vowels(s)
s.downcase.count('aeiou')
end
def is_prime(n)
return false if n < 2
(2..Math.sqrt(n).to_i).none? { |i| n % i == 0 }
end
def handler(ctx)
mat = [[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]]
data = (1..50).to_a
nested = [[1,[2,3]],[4,[5,[6,7]]],8]
result = {
tag: "big-code-ruby-ok",
fib_25: fib(25),
primes_50: sieve(50),
rot13: rot13("Hello Fission"),
caesar: caesar("heavy"),
const_sum: CONSTS.values.sum.round(2),
mat_trace: mat_trace(mat),
palindrome: palindrome?("racecar"),
distinct: count_distinct([1,2,2,3,3,3,4]),
flat_len: flatten_nested(nested).length,
checksum: checksum("fission-heavy-test"),
rsum_last: running_sum(data).last,
pascal_4: pascal_row(4),
rev_words: reverse_words("big code ruby test"),
vowels: count_vowels("fission heavy test"),
is_prime_97: is_prime(97),
groups_3: group_by_mod(data.first(30), 3),
}
{ statusCode: 200, body: result.to_json }
end
RBEOF
create_json "ruby" "$FN_BIG_RUBY" "${TMP}/big_ruby.rb"
FN_BIG_PHP="t-big-php-${RUN_ID}"
cat > "${TMP}/big_php.php" << 'PHPEOF'
<?php
$CONSTS = [];
for ($i = 0; $i < 20; $i++) $CONSTS["k{$i}"] = $i * M_PI;
function fib($n) {
$a = 0; $b = 1;
for ($i = 1; $i < $n; $i++) { $t = $a + $b; $a = $b; $b = $t; }
return $b;
}
function sieve($limit) {
$isP = array_fill(0, $limit + 1, true);
$isP[0] = $isP[1] = false;
for ($i = 2; $i * $i <= $limit; $i++)
if ($isP[$i]) for ($j = $i*$i; $j <= $limit; $j += $i) $isP[$j] = false;
return array_sum(array_map('intval', $isP));
}
function rot13($s) { return str_rot13($s); }
function caesar($s, $shift = 7) {
$r = '';
for ($i = 0; $i < strlen($s); $i++) {
$c = $s[$i];
if ($c >= 'a' && $c <= 'z') $r .= chr((ord($c) - 97 + $shift) % 26 + 97);
elseif ($c >= 'A' && $c <= 'Z') $r .= chr((ord($c) - 65 + $shift) % 26 + 65);
else $r .= $c;
}
return $r;
}
function matTrace($m) {
$s = 0;
foreach ($m as $i => $row) $s += $row[$i];
return $s;
}
function palindrome($s) {
$t = str_replace(' ', '', strtolower($s));
return $t === strrev($t);
}
function checksumStr($data) { return substr(hash('sha256', $data), 0, 16); }
function runningSum($arr) {
$t = 0; $out = [];
foreach ($arr as $v) { $t += $v; $out[] = $t; }
return $out;
}
function countDistinct($arr) { return count(array_unique($arr)); }
function groupByMod($arr, $n) {
$g = array_fill(0, $n, 0);
foreach ($arr as $v) $g[$v % $n]++;
return $g;
}
function pascalRow($n) {
$row = [1];
for ($i = 1; $i <= $n; $i++) $row[] = intdiv($row[$i-1] * ($n-$i+1), $i);
return $row;
}
function reverseWords($s) { return implode(' ', array_reverse(explode(' ', $s))); }
function countVowels($s) { return preg_match_all('/[aeiou]/i', $s); }
function isPrime($n) {
if ($n < 2) return false;
for ($i = 2; $i * $i <= $n; $i++) if ($n % $i === 0) return false;
return true;
}
function handler($ctx) {
global $CONSTS;
$mat = [[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]];
$data = range(1, 50);
$constSum = 0; foreach ($CONSTS as $v) $constSum += $v;
$rsum = runningSum($data);
$result = [
'tag' => 'big-code-php-ok',
'fib_25' => fib(25),
'primes_50' => sieve(50),
'rot13' => rot13('Hello Fission'),
'caesar' => caesar('heavy'),
'const_sum' => round($constSum, 2),
'mat_trace' => matTrace($mat),
'palindrome' => palindrome('racecar'),
'distinct' => countDistinct([1,2,2,3,3,3,4]),
'checksum' => checksumStr('fission-heavy-test'),
'rsum_last' => end($rsum),
'pascal_4' => pascalRow(4),
'rev_words' => reverseWords('big code php test'),
'vowels' => countVowels('fission heavy test'),
'is_prime_97'=> isPrime(97),
'groups_3' => groupByMod(array_slice($data, 0, 30), 3),
];
$ctx["response"]->getBody()->write(json_encode($result));
}
PHPEOF
create_json "php" "$FN_BIG_PHP" "${TMP}/big_php.php"
echo ""
echo ">>> S1: invoke больших функций — ждём cold start всех 4 языков..."
for fn_tag in \
"${FN_BIG_PY}:big-code-py-ok:Python" \
"${FN_BIG_NODE}:big-code-node-ok:Node.js" \
"${FN_BIG_RUBY}:big-code-ruby-ok:Ruby" \
"${FN_BIG_PHP}:big-code-php-ok:PHP"; do
fn="${fn_tag%%:*}"; rest="${fn_tag#*:}"; tag="${rest%%:*}"; label="${rest#*:}"
(
if wait_invoke "$SUB" "$fn" "$tag" 20 6; then
echo "OK"
else
echo "FAIL"
fi
) > "${TMP}/s1-${fn}.out" &
done
wait
for fn_tag in \
"${FN_BIG_PY}:big-code-py-ok:Python" \
"${FN_BIG_NODE}:big-code-node-ok:Node.js" \
"${FN_BIG_RUBY}:big-code-ruby-ok:Ruby" \
"${FN_BIG_PHP}:big-code-php-ok:PHP"; do
fn="${fn_tag%%:*}"; rest="${fn_tag#*:}"; tag="${rest%%:*}"; label="${rest#*:}"
result=$(cat "${TMP}/s1-${fn}.out" 2>/dev/null || echo "FAIL")
if [ "$result" = "OK" ]; then
pass "S1 $label BIG CODE invoke → 200"
else
fail "S1 $label BIG CODE invoke — не стабилизировался"
fi
done
# ═══════════════════════════════════════════════════════════════════════════
# SECTION 2: CPU BURN — рекурсивный fib, нагрузка на CPU пода
# Ищем: OOM kill, timeout, зависание пула, медленный poolmgr
# ═══════════════════════════════════════════════════════════════════════════
echo ""
echo "────────────────────────────────────────────────────────"
echo " S2: CPU BURN — рекурсивный fib (Python/Node.js/Ruby/PHP)"
echo "────────────────────────────────────────────────────────"
FN_CPU_PY="t-cpu-py-${RUN_ID}"
cat > "${TMP}/cpu_py.py" << 'PYEOF'
def fib(n):
if n < 2: return n
return fib(n-1) + fib(n-2)
def main(event, context):
result = fib(28)
return "cpu-burn-py:" + str(result), 200
PYEOF
create_json "python" "$FN_CPU_PY" "${TMP}/cpu_py.py"
FN_CPU_NODE="t-cpu-node-${RUN_ID}"
cat > "${TMP}/cpu_node.js" << 'JSEOF'
function fib(n) {
if (n < 2) return n;
return fib(n-1) + fib(n-2);
}
module.exports = async function(ctx) {
return { status: 200, body: "cpu-burn-node:" + fib(35) };
};
JSEOF
create_json "nodejs" "$FN_CPU_NODE" "${TMP}/cpu_node.js"
FN_CPU_RUBY="t-cpu-ruby-${RUN_ID}"
cat > "${TMP}/cpu_ruby.rb" << 'RBEOF'
def fib(n)
return n if n < 2
fib(n-1) + fib(n-2)
end
def handler(ctx)
{ statusCode: 200, body: "cpu-burn-ruby:#{fib(25)}" }
end
RBEOF
create_json "ruby" "$FN_CPU_RUBY" "${TMP}/cpu_ruby.rb"
FN_CPU_PHP="t-cpu-php-${RUN_ID}"
cat > "${TMP}/cpu_php.php" << 'PHPEOF'
<?php
function fib($n) {
if ($n < 2) return $n;
return fib($n-1) + fib($n-2);
}
function handler($ctx) {
$ctx["response"]->getBody()->write('cpu-burn-php:' . fib(28));
}
PHPEOF
create_json "php" "$FN_CPU_PHP" "${TMP}/cpu_php.php"
echo ""
echo ">>> S2: invoke CPU-burn функций..."
for fn_tag in \
"${FN_CPU_PY}:cpu-burn-py:Python" \
"${FN_CPU_NODE}:cpu-burn-node:Node.js" \
"${FN_CPU_RUBY}:cpu-burn-ruby:Ruby" \
"${FN_CPU_PHP}:cpu-burn-php:PHP"; do
fn="${fn_tag%%:*}"; rest="${fn_tag#*:}"; tag="${rest%%:*}"; label="${rest#*:}"
(
if wait_invoke "$SUB" "$fn" "$tag" 18 6; then
echo "OK"
else
echo "FAIL"
fi
) > "${TMP}/s2-${fn}.out" &
done
wait
for fn_tag in \
"${FN_CPU_PY}:cpu-burn-py:Python" \
"${FN_CPU_NODE}:cpu-burn-node:Node.js" \
"${FN_CPU_RUBY}:cpu-burn-ruby:Ruby" \
"${FN_CPU_PHP}:cpu-burn-php:PHP"; do
fn="${fn_tag%%:*}"; rest="${fn_tag#*:}"; tag="${rest%%:*}"; label="${rest#*:}"
result=$(cat "${TMP}/s2-${fn}.out" 2>/dev/null || echo "FAIL")
if [ "$result" = "OK" ]; then
pass "S2 $label CPU BURN invoke → 200"
else
fail "S2 $label CPU BURN — не стабилизировался"
fi
done
# ═══════════════════════════════════════════════════════════════════════════
# SECTION 3: БОЛЬШОЙ ОТВЕТ — 50KB body
# Ищем: truncation, proxy memory issues, timeout на больших ответах
# ═══════════════════════════════════════════════════════════════════════════
echo ""
echo "────────────────────────────────────────────────────────"
echo " S3: БОЛЬШОЙ ОТВЕТ — 50KB (Python + Node.js)"
echo "────────────────────────────────────────────────────────"
FN_RESP_PY="t-resp-py-${RUN_ID}"
cat > "${TMP}/resp_py.py" << 'PYEOF'
def main(event, context):
chunk = "FISSION-HEAVY-DATA-" * 100 # 1900 chars
data = (chunk * 27)[:50000] # ровно 50000 chars
return "bigresponse-py-ok:" + data[:20] + "..." + str(len(data)), 200
PYEOF
create_json "python" "$FN_RESP_PY" "${TMP}/resp_py.py"
FN_RESP_NODE="t-resp-node-${RUN_ID}"
cat > "${TMP}/resp_node.js" << 'JSEOF'
module.exports = async function(ctx) {
const chunk = "FISSION-HEAVY-DATA-".repeat(100);
const data = (chunk.repeat(27)).slice(0, 50000);
return { status: 200, body: "bigresponse-node-ok:" + data.slice(0,20) + "..." + data.length };
};
JSEOF
create_json "nodejs" "$FN_RESP_NODE" "${TMP}/resp_node.js"
echo ""
echo ">>> S3: invoke и проверка размера ответа..."
for fn_tag in \
"${FN_RESP_PY}:bigresponse-py-ok:Python" \
"${FN_RESP_NODE}:bigresponse-node-ok:Node.js"; do
fn="${fn_tag%%:*}"; rest="${fn_tag#*:}"; tag="${rest%%:*}"; label="${rest#*:}"
if wait_invoke "$SUB" "$fn" "$tag" 15 6; then
# Проверяем реальный размер ответа
RESP=$(curl -s --max-time 60 -X POST "${BASE}/functions/${fn}/invoke" \
-H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" -d '{}')
RAW=$(json_field "$RESP" response_raw)
RAW_LEN=${#RAW}
echo " response_raw length = $RAW_LEN chars"
if [ "$RAW_LEN" -gt 40000 ]; then
pass "S3 $label BIG RESPONSE size=$RAW_LEN (>40000)"
else
fail "S3 $label BIG RESPONSE слишком мал: size=$RAW_LEN"
fi
else
fail "S3 $label BIG RESPONSE — invoke не стабилизировался"
fi
done
# ═══════════════════════════════════════════════════════════════════════════
# SECTION 4: АРХИВЫ ИЗ ZIP — создаём архивы на лету, все 4 языка
# Ищем: баги пути upload vs inline, entrypoint defaults, storagesvc PV
# ═══════════════════════════════════════════════════════════════════════════
echo ""
echo "────────────────────────────────────────────────────────"
echo " S4: АРХИВ ИЗ ZIP (Python / Node.js / Ruby / PHP)"
echo "────────────────────────────────────────────────────────"
# --- Создаём ZIP-архивы на лету через python3 ---
# Python archive: main.py (entrypoint по умолчанию: main.main)
python3 - > "${TMP}/arc-py.zip" << 'EOF'
import zipfile, io, sys
buf = io.BytesIO()
code = """import json
def main(event, context):
return json.dumps({"tag": "archive-py-ok", "fib25": 75025}), 200
"""
with zipfile.ZipFile(buf, 'w', zipfile.ZIP_DEFLATED) as zf:
zf.writestr('main.py', code)
sys.stdout.buffer.write(buf.getvalue())
EOF
# Node.js archive: main.js (entrypoint: main → файл main.js)
python3 - > "${TMP}/arc-node.zip" << 'EOF'
import zipfile, io, sys
buf = io.BytesIO()
code = """module.exports = async function(ctx) {
return { status: 200, body: JSON.stringify({ tag: "archive-node-ok", fib25: 75025 }) };
};
"""
pkg = '{"name":"heavy-node","version":"1.0.0"}'
with zipfile.ZipFile(buf, 'w', zipfile.ZIP_DEFLATED) as zf:
zf.writestr('main.js', code)
zf.writestr('package.json', pkg)
sys.stdout.buffer.write(buf.getvalue())
EOF
# Ruby archive: handler.rb (entrypoint: handler)
python3 - > "${TMP}/arc-ruby.zip" << 'EOF'
import zipfile, io, sys
buf = io.BytesIO()
code = """require 'json'
def handler(ctx)
{ statusCode: 200, body: JSON.generate({ tag: "archive-ruby-ok", fib25: 75025 }) }
end
"""
with zipfile.ZipFile(buf, 'w', zipfile.ZIP_DEFLATED) as zf:
zf.writestr('handler.rb', code)
sys.stdout.buffer.write(buf.getvalue())
EOF
# PHP archive: main.php (entrypoint: main.php::handler)
python3 - > "${TMP}/arc-php.zip" << 'EOF'
import zipfile, io, sys
buf = io.BytesIO()
code = """<?php
function handler($ctx) {
$ctx[\"response\"]->getBody()->write(json_encode(['tag' => 'archive-php-ok', 'fib25' => 75025]));
}
"""
with zipfile.ZipFile(buf, 'w', zipfile.ZIP_DEFLATED) as zf:
zf.writestr('main.php', code)
sys.stdout.buffer.write(buf.getvalue())
EOF
# Загружаем архивы через multipart POST /functions
declare -A ARC_LANGS=( ["py"]="python" ["node"]="nodejs" ["ruby"]="ruby" ["php"]="php" )
declare -A ARC_TAGS=( ["py"]="archive-py-ok" ["node"]="archive-node-ok" ["ruby"]="archive-ruby-ok" ["php"]="archive-php-ok" )
for lang_short in py node ruby php; do
fn="t-arc-${lang_short}-${RUN_ID}"
reg "$fn"
lang="${ARC_LANGS[$lang_short]}"
arcfile="${TMP}/arc-${lang_short}.zip"
R=$(curl -s -w "\n%{http_code}" -X POST "${BASE}/functions" \
-H "X-Auth-Token: ${SUB}" \
-F "name=${fn}" \
-F "language=${lang}" \
-F "archive=@${arcfile};type=application/zip" 2>/dev/null)
CODE=$(echo "$R" | tail -1)
BODY=$(echo "$R" | head -n -1)
check_http "$CODE" "201" "S4 ${lang_short^^} ARCHIVE upload → 201"
if [ "$CODE" != "201" ]; then
echo " upload error: $(printf '%s' "$BODY" | head -c 200)"
fi
done
echo ""
echo ">>> S4: invoke архивных функций — ждём cold start..."
for lang_short in py node ruby php; do
fn="t-arc-${lang_short}-${RUN_ID}"
tag="${ARC_TAGS[$lang_short]}"
(
if wait_invoke "$SUB" "$fn" "$tag" 20 6; then
echo "OK"
else
echo "FAIL"
fi
) > "${TMP}/s4-${fn}.out" &
done
wait
for lang_short in py node ruby php; do
fn="t-arc-${lang_short}-${RUN_ID}"
tag="${ARC_TAGS[$lang_short]}"
result=$(cat "${TMP}/s4-${fn}.out" 2>/dev/null || echo "FAIL")
if [ "$result" = "OK" ]; then
pass "S4 ${lang_short^^} ARCHIVE invoke → 200"
else
fail "S4 ${lang_short^^} ARCHIVE invoke — не стабилизировался"
fi
done
# ═══════════════════════════════════════════════════════════════════════════
# SECTION 5: БЫСТРЫЕ СЕРИИ — 12 последовательных invoke одной функции
# Ищем: poolmgr reuse bugs, утечки памяти, деградацию latency
# ═══════════════════════════════════════════════════════════════════════════
echo ""
echo "────────────────────────────────────────────────────────"
echo " S5: БЫСТРЫЕ СЕРИИ — 12x invoke подряд (Python)"
echo "────────────────────────────────────────────────────────"
FN_RAPID="t-rapid-py-${RUN_ID}"
cat > "${TMP}/rapid_py.py" << 'PYEOF'
import time
_count = 0
def main(event, context):
global _count
_count += 1
return "rapid-ok:{}".format(_count), 200
PYEOF
create_json "python" "$FN_RAPID" "${TMP}/rapid_py.py"
echo ">>> Ждём cold start..."
if wait_invoke "$SUB" "$FN_RAPID" "rapid-ok" 15 5; then
echo ">>> S5: Запускаем 12 последовательных invoke..."
RAPID_OK=0
RAPID_FAIL=0
LATENCIES=()
for i in $(seq 1 12); do
T_START=$(date +%s%3N)
RESP=$(curl -s --max-time 20 -X POST "${BASE}/functions/${FN_RAPID}/invoke" \
-H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" -d '{}')
T_END=$(date +%s%3N)
LAT=$(( T_END - T_START ))
STATUS=$(json_field "$RESP" status)
RAW=$(json_field "$RESP" response_raw)
echo " invoke ${i}/12: status=$STATUS raw=$(printf '%s' "$RAW" | head -c 40) latency=${LAT}ms"
if [ "$STATUS" = "200" ]; then
RAPID_OK=$((RAPID_OK+1))
LATENCIES+=("$LAT")
else
RAPID_FAIL=$((RAPID_FAIL+1))
fi
done
[ "$RAPID_FAIL" -eq 0 ] && pass "S5 RAPID FIRE: 12/12 invoke → 200" \
|| fail "S5 RAPID FIRE: ${RAPID_FAIL}/12 упали"
# Статистика latency
if [ "${#LATENCIES[@]}" -gt 0 ]; then
LAT_MIN="${LATENCIES[0]}"; LAT_MAX="${LATENCIES[0]}"
for lat in "${LATENCIES[@]}"; do
[ "$lat" -lt "$LAT_MIN" ] && LAT_MIN="$lat"
[ "$lat" -gt "$LAT_MAX" ] && LAT_MAX="$lat"
done
echo " latency min=${LAT_MIN}ms max=${LAT_MAX}ms"
fi
else
fail "S5 RAPID FIRE — cold start не прошёл, тест пропущен"
fi
# ═══════════════════════════════════════════════════════════════════════════
# SECTION 6: ПАРАЛЛЕЛЬНЫЕ ВЫЗОВЫ — 5 параллельных invoke на каждый язык
# Ищем: race conditions, конфликты при pod scheduling, deadlocks
# ═══════════════════════════════════════════════════════════════════════════
echo ""
echo "────────────────────────────────────────────────────────"
echo " S6: ПАРАЛЛЕЛЬНЫЕ ВЫЗОВЫ — 5 одновременных invoke × 4 языка"
echo "────────────────────────────────────────────────────────"
PARALLEL_N=5
# Используем уже тёплые функции из S2 (CPU burn — уже invoke'd)
declare -A PAR_FNS=( ["Python"]="$FN_CPU_PY" ["Node.js"]="$FN_CPU_NODE" ["Ruby"]="$FN_CPU_RUBY" ["PHP"]="$FN_CPU_PHP" )
declare -A PAR_TAGS=( ["Python"]="cpu-burn-py" ["Node.js"]="cpu-burn-node" ["Ruby"]="cpu-burn-ruby" ["PHP"]="cpu-burn-php" )
for lang in "Python" "Node.js" "Ruby" "PHP"; do
fn="${PAR_FNS[$lang]}"
tag="${PAR_TAGS[$lang]}"
echo ""
echo ">>> S6 $lang: $PARALLEL_N параллельных invoke..."
for i in $(seq 1 "$PARALLEL_N"); do
(
RESP=$(curl -s --max-time 60 -X POST "${BASE}/functions/${fn}/invoke" \
-H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" -d '{}')
STATUS=$(json_field "$RESP" status)
RAW=$(json_field "$RESP" response_raw)
if [ "$STATUS" = "200" ] && printf '%s' "$RAW" | grep -qF "$tag"; then
echo "OK" > "${TMP}/par-${lang}-${i}.out"
else
echo "FAIL:status=${STATUS}" > "${TMP}/par-${lang}-${i}.out"
fi
) &
done
wait
PAR_OK=0; PAR_FAIL=0
for i in $(seq 1 "$PARALLEL_N"); do
result=$(cat "${TMP}/par-${lang}-${i}.out" 2>/dev/null || echo "FAIL:no-file")
if [ "$result" = "OK" ]; then
PAR_OK=$((PAR_OK+1))
else
PAR_FAIL=$((PAR_FAIL+1))
echo " invoke $i: $result"
fi
done
[ "$PAR_FAIL" -eq 0 ] \
&& pass "S6 $lang PARALLEL: ${PARALLEL_N}/${PARALLEL_N} invoke → 200" \
|| fail "S6 $lang PARALLEL: ${PAR_FAIL}/${PARALLEL_N} упали"
done
# ═══════════════════════════════════════════════════════════════════════════
# SECTION 7: МЕДЛЕННЫЕ ФУНКЦИИ — sleep 6s, Python + Node.js
# Ищем: router timeout, poolmgr idle eviction, 504 под нагрузкой
# ═══════════════════════════════════════════════════════════════════════════
echo ""
echo "────────────────────────────────────────────────────────"
echo " S7: МЕДЛЕННЫЕ ФУНКЦИИ — sleep(6s) × Python + Node.js"
echo "────────────────────────────────────────────────────────"
FN_SLOW_PY="t-slow-py-${RUN_ID}"
cat > "${TMP}/slow_py.py" << 'PYEOF'
import time
def main(event, context):
time.sleep(6)
return "slow-py-ok", 200
PYEOF
create_json "python" "$FN_SLOW_PY" "${TMP}/slow_py.py"
FN_SLOW_NODE="t-slow-node-${RUN_ID}"
cat > "${TMP}/slow_node.js" << 'JSEOF'
module.exports = async function(ctx) {
await new Promise(r => setTimeout(r, 6000));
return { status: 200, body: "slow-node-ok" };
};
JSEOF
create_json "nodejs" "$FN_SLOW_NODE" "${TMP}/slow_node.js"
echo ""
echo ">>> S7: invoke (ждём cold start + 6s sleep)..."
for fn_tag in \
"${FN_SLOW_PY}:slow-py-ok:Python" \
"${FN_SLOW_NODE}:slow-node-ok:Node.js"; do
fn="${fn_tag%%:*}"; rest="${fn_tag#*:}"; tag="${rest%%:*}"; label="${rest#*:}"
T_START=$(date +%s%3N)
if wait_invoke "$SUB" "$fn" "$tag" 15 8; then
T_END=$(date +%s%3N)
LAT=$(( T_END - T_START ))
pass "S7 $label SLOW(6s) → 200 за ~${LAT}ms total"
# Повторный invoke (warm pod): должно занять ≥6s
T2=$(date +%s%3N)
RESP=$(curl -s --max-time 30 -X POST "${BASE}/functions/${fn}/invoke" \
-H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" -d '{}')
T2E=$(date +%s%3N)
LAT2=$(( T2E - T2 ))
STATUS=$(json_field "$RESP" status)
RAW=$(json_field "$RESP" response_raw)
echo " warm invoke: status=$STATUS latency=${LAT2}ms raw=$(printf '%s' "$RAW" | head -c 40)"
if [ "$STATUS" = "200" ] && [ "$LAT2" -ge 5000 ]; then
pass "S7 $label SLOW warm invoke → 200 latency=${LAT2}ms (≥5s)"
elif [ "$STATUS" = "200" ]; then
fail "S7 $label SLOW warm слишком быстро: ${LAT2}ms — sleep не сработал?"
else
fail "S7 $label SLOW warm invoke → status=$STATUS"
fi
else
fail "S7 $label SLOW — cold start не прошёл"
fi
done
# ═══════════════════════════════════════════════════════════════════════════
# SECTION 8: ДАВЛЕНИЕ ПО ПАМЯТИ — аллокация 20MB, Python + Node.js
# Ищем: OOM kill на поде, зависание, некорректный ответ
# ═══════════════════════════════════════════════════════════════════════════
echo ""
echo "────────────────────────────────────────────────────────"
echo " S8: ПАМЯТЬ — аллокация 20MB (Python + Node.js)"
echo "────────────────────────────────────────────────────────"
FN_MEM_PY="t-mem-py-${RUN_ID}"
cat > "${TMP}/mem_py.py" << 'PYEOF'
def main(event, context):
# Аллоцируем 20MB и суммируем (чтобы не оптимизировалось)
data = bytearray(20 * 1024 * 1024)
for i in range(0, len(data), 4096):
data[i] = i % 256
checksum = sum(data[i] for i in range(0, len(data), 4096)) % 65536
return "mem-py-ok:{}:{}".format(len(data), checksum), 200
PYEOF
create_json "python" "$FN_MEM_PY" "${TMP}/mem_py.py"
FN_MEM_NODE="t-mem-node-${RUN_ID}"
cat > "${TMP}/mem_node.js" << 'JSEOF'
module.exports = async function(ctx) {
const size = 20 * 1024 * 1024;
const buf = Buffer.alloc(size);
for (let i = 0; i < size; i += 4096) buf[i] = i % 256;
let checksum = 0;
for (let i = 0; i < size; i += 4096) checksum = (checksum + buf[i]) % 65536;
return { status: 200, body: `mem-node-ok:${size}:${checksum}` };
};
JSEOF
create_json "nodejs" "$FN_MEM_NODE" "${TMP}/mem_node.js"
echo ""
echo ">>> S8: invoke (20MB аллокация)..."
for fn_tag in \
"${FN_MEM_PY}:mem-py-ok:Python" \
"${FN_MEM_NODE}:mem-node-ok:Node.js"; do
fn="${fn_tag%%:*}"; rest="${fn_tag#*:}"; tag="${rest%%:*}"; label="${rest#*:}"
if wait_invoke "$SUB" "$fn" "$tag" 18 6; then
pass "S8 $label MEMORY(20MB) → 200"
# Проверяем что данные в ответе правильные
RESP=$(curl -s --max-time 30 -X POST "${BASE}/functions/${fn}/invoke" \
-H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" -d '{}')
RAW=$(json_field "$RESP" response_raw)
if printf '%s' "$RAW" | grep -qE "$tag:[0-9]+:[0-9]+"; then
pass "S8 $label MEMORY ответ содержит checksum: $(printf '%s' "$RAW" | head -c 60)"
else
fail "S8 $label MEMORY ответ некорректен: $(printf '%s' "$RAW" | head -c 60)"
fi
else
fail "S8 $label MEMORY(20MB) — не вернулась (OOM kill?)"
fi
done
# ═══════════════════════════════════════════════════════════════════════════
print_summary