Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 60 additions & 1 deletion install/ci-vm/ci-linux/ci/runCI
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@

DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )

# Enable coredump capture
ulimit -c unlimited
mkdir -p /tmp/coredumps
echo "/tmp/coredumps/core.%e.%p" | sudo tee /proc/sys/kernel/core_pattern > /dev/null

if [ ! -f "$DIR/variables" ]; then
# No variable file defined
sudo shutdown -h now
Expand Down Expand Up @@ -124,8 +129,62 @@ if [ -e "${dstDir}/ccextractor" ]; then
./ccextractor --version >> "${logFile}" 2>&1
echo "=== End Version Info ===" >> "${logFile}"
postStatus "testing" "Running tests"
ccextractor_path="$(pwd)/ccextractor"
combined_stdout="/tmp/combined_stdout.log"
: > "${combined_stdout}"

# Create a wrapper script that tees stdout/stderr to a combined log
wrapper_path="$(pwd)/ccextractor_wrapper"
cat > "${wrapper_path}" << 'WRAPPER_EOF'
#!/bin/bash
COMBINED_LOG="/tmp/combined_stdout.log"
REAL_BINARY="PLACEHOLDER_BINARY"
EXIT_CODE_FILE=$(mktemp)
echo "=== TEST INVOCATION: $@ ===" >> "$COMBINED_LOG"
{ "$REAL_BINARY" "$@" 2>&1; echo $? > "$EXIT_CODE_FILE"; } | tee -a "$COMBINED_LOG"
exit_code=$(cat "$EXIT_CODE_FILE")
rm -f "$EXIT_CODE_FILE"
echo "=== EXIT CODE: ${exit_code} ===" >> "$COMBINED_LOG"
echo "" >> "$COMBINED_LOG"
exit $exit_code
WRAPPER_EOF
sed -i "s|PLACEHOLDER_BINARY|${ccextractor_path}|" "${wrapper_path}"
chmod +x "${wrapper_path}"

executeCommand cd ${suiteDstDir}
executeCommand ${tester} --debug --entries "${testFile}" --executable "ccextractor" --tempfolder "${tempFolder}" --timeout 600 --reportfolder "${reportFolder}" --resultfolder "${resultFolder}" --samplefolder "${sampleFolder}" --method Server --url "${reportURL}"
executeCommand ${tester} --debug --entries "${testFile}" --executable "${wrapper_path}" --tempfolder "${tempFolder}" --timeout 600 --reportfolder "${reportFolder}" --resultfolder "${resultFolder}" --samplefolder "${sampleFolder}" --method Server --url "${reportURL}"

# Upload artifacts through the Sample Platform server
upload_artifact() {
local file_path="$1"
local artifact_name="$2"
if [ -f "$file_path" ]; then
local http_code
http_code=$(curl -s -A "${userAgent}" \
--form "type=artifact" \
--form "name=${artifact_name}" \
--form "file=@${file_path}" \
-w "%{http_code}" -o /dev/null \
"${reportURL}" 2>/dev/null)
if [ -z "$http_code" ] || [ "$http_code" -lt 200 ] || [ "$http_code" -ge 300 ]; then
echo "Artifact upload failed for ${artifact_name}: HTTP ${http_code:-no_response}" >> "${logFile}"
fi
fi
}

upload_artifact "$ccextractor_path" "ccextractor"

# Upload combined stdout log
upload_artifact "${combined_stdout}" "combined_stdout.log"

# Upload coredumps if any
for core_file in /tmp/coredumps/core.*; do
if [ -f "$core_file" ]; then
upload_artifact "$core_file" "coredump"
break
fi
done

sendLogFile
postStatus "completed" "Ran all tests"

Expand Down
55 changes: 49 additions & 6 deletions mod_ci/controllers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1194,8 +1194,7 @@ def create_instance(compute, project, zone, test, reportURL) -> Dict:
startup_script = f.read()
metadata_items = [
{'key': 'startup-script', 'value': startup_script},
{'key': 'reportURL', 'value': reportURL},
{'key': 'bucket', 'value': config.get('GCS_BUCKET_NAME', '')}
{'key': 'reportURL', 'value': reportURL}
]
elif test.platform == TestPlatform.windows:
image_response = compute.images().getFromFamily(project=config.get('WINDOWS_INSTANCE_PROJECT_NAME', ''),
Expand All @@ -1216,8 +1215,7 @@ def create_instance(compute, project, zone, test, reportURL) -> Dict:
{'key': 'windows-startup-script-ps1', 'value': startup_script},
{'key': 'service_account', 'value': service_account},
{'key': 'rclone_conf', 'value': rclone_conf},
{'key': 'reportURL', 'value': reportURL},
{'key': 'bucket', 'value': config.get('GCS_BUCKET_NAME', '')}
{'key': 'reportURL', 'value': reportURL}
]
source_disk_image = image_response['selfLink']

Expand Down Expand Up @@ -2344,6 +2342,11 @@ def progress_reporter(test_id, token):
if not upload_type_request(log, test_id, repo_folder, test, request):
return "EMPTY"

elif request.form['type'] == 'artifact':
log.info(f'[PROGRESS_REPORTER][Test: {test_id}] Artifact upload')
if not artifact_upload_request(log, test_id, request):
return "EMPTY"

elif request.form['type'] == 'finish':
log.info(f'[PROGRESS_REPORTER][Test: {test_id}] Test finished')
finish_type_request(log, test_id, test, request)
Expand Down Expand Up @@ -2635,7 +2638,7 @@ def upload_log_type_request(log, test_id, repo_folder, test, request) -> bool:
uploaded_file.save(temp_path)
final_path = os.path.join(repo_folder, 'LogFiles', f"{test.id}.txt")

os.rename(temp_path, final_path)
os.replace(temp_path, final_path)
log.debug("Stored log file")
return True

Expand Down Expand Up @@ -2681,7 +2684,7 @@ def upload_type_request(log, test_id, repo_folder, test, request) -> bool:
results_dir = os.path.join(repo_folder, 'TestResults')
os.makedirs(results_dir, exist_ok=True)
final_path = os.path.join(results_dir, f'{file_hash}{file_extension}')
os.rename(temp_path, final_path)
os.replace(temp_path, final_path)
rto = RegressionTestOutput.query.filter(
RegressionTestOutput.id == request.form['test_file_id']).first()
result_file = TestResultFile(test.id, request.form['test_id'], rto.id, rto.correct, file_hash)
Expand All @@ -2693,6 +2696,46 @@ def upload_type_request(log, test_id, repo_folder, test, request) -> bool:
return False


# Allowed artifact names that the VM can upload
from mod_test.controllers import CCEXTRACTOR_LINUX_BINARY, CCEXTRACTOR_WIN_BINARY
ALLOWED_ARTIFACT_NAMES = {CCEXTRACTOR_LINUX_BINARY, CCEXTRACTOR_WIN_BINARY, 'combined_stdout.log', 'coredump'}


def artifact_upload_request(log, test_id, request) -> bool:
"""
Handle artifact upload from the CI VM.

Validates the artifact name against an allow-list, then uploads
the file to GCS under test_artifacts/{test_id}/{name}.

:param log: logger
:type log: Logger
:param test_id: The id of the test to update.
:type test_id: int
:param request: Request parameters
:type request: Request
:return: True if upload succeeded, False otherwise.
:rtype: bool
"""
from run import storage_client_bucket

artifact_name = request.form.get('name', '')
if artifact_name not in ALLOWED_ARTIFACT_NAMES:
log.warning(f"[Test: {test_id}] Rejected artifact upload with disallowed name: {artifact_name}")
return False

if 'file' not in request.files:
log.warning(f"[Test: {test_id}] Artifact upload missing file")
return False

uploaded_file = request.files['file']
blob_path = f'test_artifacts/{test_id}/{artifact_name}'
blob = storage_client_bucket.blob(blob_path)
blob.upload_from_file(uploaded_file.stream)
log.info(f"[Test: {test_id}] Artifact '{artifact_name}' uploaded to {blob_path}")
return True


def finish_type_request(log, test_id, test, request):
"""
Handle finish request type for progress reporter.
Expand Down
Loading