This is an automated email from the ASF dual-hosted git repository.
lewismc pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nutch.git
The following commit(s) were added to refs/heads/master by this push:
new 6990f21c9 NUTCH-3194 Add Jenkinsfile for single-node Hadoop smoke test
job (#946)
6990f21c9 is described below
commit 6990f21c974c2b495633ebb9008c97c3c0b65a9e
Author: Lewis John McGibbney <[email protected]>
AuthorDate: Wed Aug 5 15:16:02 2026 -0700
NUTCH-3194 Add Jenkinsfile for single-node Hadoop smoke test job (#946)
---
.github/workflows/master-build.yml | 25 +++
Jenkinsfile.smoke-test-single-node-hadoop | 321 ++++++++++++++++++++++++++++++
2 files changed, 346 insertions(+)
diff --git a/.github/workflows/master-build.yml
b/.github/workflows/master-build.yml
index 74072f591..02d6faf9a 100644
--- a/.github/workflows/master-build.yml
+++ b/.github/workflows/master-build.yml
@@ -114,6 +114,31 @@ jobs:
if: steps.filter.outputs.openapi == 'true'
run: ./node_modules/.bin/lint-openapi openapi.yaml
+ # Declarative Pipeline syntax check (jenkinsfilelint --local; Docker
required).
+ # Path-filtered: only runs when Jenkinsfile* changes. Local mode validates
+ # vanilla Declarative syntax, not every plugin on ci-builds.apache.org.
+ jenkinsfile-lint:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v7
+ - uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706
+ id: filter
+ with:
+ filters: |
+ jenkinsfile:
+ - 'Jenkinsfile*'
+ - name: Set up Python
+ if: steps.filter.outputs.jenkinsfile == 'true'
+ uses: actions/setup-python@v7
+ with:
+ python-version: '3.13'
+ - name: Install jenkinsfilelint
+ if: steps.filter.outputs.jenkinsfile == 'true'
+ run: pip install jenkinsfilelint==1.5.1
+ - name: Validate Jenkinsfile Declarative Pipeline syntax
+ if: steps.filter.outputs.jenkinsfile == 'true'
+ run: jenkinsfilelint --local --include 'Jenkinsfile*'
Jenkinsfile.smoke-test-single-node-hadoop
+
# Build verification on JDK 17 and 21 (bytecode target Java 17)
build:
strategy:
diff --git a/Jenkinsfile.smoke-test-single-node-hadoop
b/Jenkinsfile.smoke-test-single-node-hadoop
new file mode 100644
index 000000000..6c208a498
--- /dev/null
+++ b/Jenkinsfile.smoke-test-single-node-hadoop
@@ -0,0 +1,321 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Nutch Smoke Test — single-node (pseudo-distributed) Hadoop cluster.
+//
+// Trigger with "Build with Parameters":
+// GIT_REF — ref to build from https://github.com/apache/nutch.git (default:
master).
+// Branches: NUTCH-3026, master
+// Tags: release-1.23 (preferred if the same name exists as a branch)
+// PRs: pr/866 (fetches pull/866/head; do not pass a bare
number)
+// Blank/whitespace GIT_REF is treated as master.
+// EMAIL_RECIPIENT — optional job-result notification address
+// HADOOP_VERSION — Hadoop tarball version to download (default: 3.4.2)
+//
+// Paste this script into the ASF Jenkins job configure page when updating the
live job.
+// Landing Jenkinsfile(s) in the apache/nutch git repo is tracked separately.
+
+pipeline {
+ agent { label 'ubuntu' }
+ parameters {
+ string(
+ name: 'GIT_REF',
+ defaultValue: 'master',
+ description: 'Git ref to build from
https://github.com/apache/nutch.git. Examples: master (default;
blank/whitespace also uses master); branch NUTCH-3026; tag release-1.23; PR
pr/866 (not a bare PR number). If a name exists as both a tag and a branch, the
tag is used.'
+ )
+ string(name: 'EMAIL_RECIPIENT', defaultValue: '', description: 'Email
address for job result notification (optional)')
+ string(name: 'HADOOP_VERSION', defaultValue: '3.5.0', description:
'Hadoop version to download (e.g., 3.X.X)')
+ }
+ environment {
+ HADOOP_HOME = "${WORKSPACE}/hadoop-${params.HADOOP_VERSION}"
+ NUTCH_HOME = "${WORKSPACE}/nutch/runtime/deploy"
+ JAVA_HOME = "/home/jenkins/tools/java/latest17"
+ CACHE_DIR = "${WORKSPACE}/cache/hadoop"
+ HADOOP_TAR = "hadoop-${params.HADOOP_VERSION}.tar.gz"
+ HADOOP_CHECKSUM = "hadoop-${params.HADOOP_VERSION}.tar.gz.sha512"
+ HADOOP_DIR = "hadoop-${params.HADOOP_VERSION}"
+ DOWNLOAD_URL =
"https://dlcdn.apache.org/hadoop/common/hadoop-${params.HADOOP_VERSION}"
+ PATH =
"${WORKSPACE}/hadoop-${params.HADOOP_VERSION}/bin:${WORKSPACE}/hadoop-${params.HADOOP_VERSION}/sbin:${WORKSPACE}/nutch/runtime/deploy/bin:${env.PATH}"
+ }
+ stages {
+ stage('Resolve GIT_REF') {
+ steps {
+ script {
+ def gitRef = (params.GIT_REF ?: '').trim()
+ if (!gitRef) {
+ gitRef = 'master'
+ }
+ if (!(gitRef ==~ /^(pr\/[0-9]+|[A-Za-z0-9._\/-]+)$/)) {
+ error("Invalid GIT_REF '${gitRef}'. Use a branch or
tag name (e.g. master, NUTCH-3026, release-1.23) or pr/<id> (e.g. pr/866).")
+ }
+ env.RESOLVED_GIT_REF = gitRef
+ currentBuild.displayName = "#${env.BUILD_NUMBER} ·
${gitRef}"
+ currentBuild.description = "Git Ref: ${gitRef}"
+ echo "Resolved GIT_REF=${gitRef}"
+ }
+ }
+ }
+ stage('Checkout nutch-test-single-node-cluster') {
+ steps {
+ git url:
'https://github.com/sebastian-nagel/nutch-test-single-node-cluster.git',
+ branch: 'main'
+ }
+ }
+ stage("Restore Hadoop from Cache") {
+ steps {
+ sh """
+ echo "Creating cache directory if it doesn't exist..."
+ mkdir -p ${CACHE_DIR}
+
+ # Restore cached files
+ if [ -f "${CACHE_DIR}/${HADOOP_TAR}" ]; then
+ echo "Restoring Hadoop tarball from cache..."
+ cp "${CACHE_DIR}/${HADOOP_TAR}" .
+ fi
+
+ if [ -f "${CACHE_DIR}/${HADOOP_CHECKSUM}" ]; then
+ echo "Restoring checksum from cache..."
+ cp "${CACHE_DIR}/${HADOOP_CHECKSUM}" .
+ fi
+
+ if [ -d "${CACHE_DIR}/${HADOOP_DIR}" ]; then
+ echo "Restoring Hadoop directory from cache..."
+ cp -r "${CACHE_DIR}/${HADOOP_DIR}" .
+ fi
+ """
+ }
+ }
+ stage("Download and Install Hadoop") {
+ steps {
+ script {
+ def stageName = "Download and Install Hadoop
${params.HADOOP_VERSION}"
+ stage(stageName) {
+ sh """
+ # Download tarball if it doesn't exist
+ if [ ! -f "${HADOOP_TAR}" ]; then
+ echo "Downloading Hadoop
${params.HADOOP_VERSION}..."
+ wget ${DOWNLOAD_URL}/${HADOOP_TAR}
+
+ echo "Downloading checksum file..."
+ wget ${DOWNLOAD_URL}/${HADOOP_CHECKSUM}
+ else
+ echo "Hadoop tarball already exists (restored
from cache)"
+
+ if [ ! -f "${HADOOP_CHECKSUM}" ]; then
+ echo "Downloading checksum file..."
+ wget ${DOWNLOAD_URL}/${HADOOP_CHECKSUM}
+ fi
+ fi
+
+ # Verify checksum
+ echo "Verifying SHA-512 checksum..."
+ if sha512sum -c ${HADOOP_CHECKSUM}; then
+ echo "Checksum verification PASSED"
+ else
+ echo "Checksum verification FAILED"
+ echo "Removing corrupted tarball..."
+ rm -f ${HADOOP_TAR}
+ # Also remove from cache
+ rm -f ${CACHE_DIR}/${HADOOP_TAR}
+ exit 1
+ fi
+
+ # Update cache with latest files
+ echo "Updating cache..."
+ cp -u ${HADOOP_TAR} ${CACHE_DIR}/
+ cp -u ${HADOOP_CHECKSUM} ${CACHE_DIR}/
+
+ # Update Hadoop configuration (Ubuntu-compatible
sed)
+ sed -i
"s|^HADOOP_VERSION=.*|HADOOP_VERSION=${params.HADOOP_VERSION}|"
hadoop_install_config.sh
+ sed -i
"s|^HADOOP_HOME=.*|HADOOP_HOME=${HADOOP_HOME}|" hadoop_install_config.sh
+
+ # Comment out chown/sudo chown line before running
install script
+ echo "Commenting out chown command in
install_hadoop.sh..."
+ sed -i 's|^sudo chown|#sudo chown|'
install_hadoop.sh
+
+ ./install_hadoop.sh
+
+ if [ ! -d "${CACHE_DIR}/${HADOOP_DIR}" ]; then
+ echo "Caching extracted Hadoop directory..."
+ cp -r ${HADOOP_HOME} ${CACHE_DIR}/
+ fi
+ . ./hadoop_install_config.sh
+ """
+ }
+ }
+ }
+ }
+ stage('Clone and Build Nutch') {
+ steps {
+ dir("nutch") {
+ git url: 'https://github.com/apache/nutch.git', branch:
'master'
+ sh """
+ set -e
+ REF='${env.RESOLVED_GIT_REF}'
+
+ if echo "\${REF}" | grep -Eq '^pr/[0-9]+\$'; then
+ PR_ID="\${REF#pr/}"
+ echo "Resolving GIT_REF=\${REF} as pull request
\${PR_ID}..."
+ git fetch origin "pull/\${PR_ID}/head:pr-\${PR_ID}"
+ git checkout "pr-\${PR_ID}"
+ echo "Checked out pull request \${PR_ID}"
+ else
+ echo "Fetching tags and heads to resolve
GIT_REF=\${REF}..."
+ git fetch origin --tags
+ git fetch origin
'+refs/heads/*:refs/remotes/origin/*'
+
+ if git rev-parse "refs/tags/\${REF}" >/dev/null
2>&1; then
+ echo "Resolving GIT_REF=\${REF} as tag
(preferred over same-named branch)..."
+ git checkout "refs/tags/\${REF}"
+ echo "Checked out tag \${REF}"
+ elif git rev-parse "refs/remotes/origin/\${REF}"
>/dev/null 2>&1; then
+ echo "Resolving GIT_REF=\${REF} as branch..."
+ git checkout -B "\${REF}" "origin/\${REF}"
+ echo "Checked out branch \${REF}"
+ elif echo "\${REF}" | grep -Eq '^[0-9a-f]{40}\$' \\
+ && git rev-parse --verify "\${REF}^{commit}"
>/dev/null 2>&1; then
+ echo "Resolving GIT_REF=\${REF} as commit..."
+ git checkout "\${REF}"
+ echo "Checked out commit \${REF}"
+ else
+ echo "ERROR: GIT_REF '\${REF}' not found as
tag, branch, or commit on https://github.com/apache/nutch.git"
+ exit 1
+ fi
+ fi
+
+ echo "HEAD=\$(git rev-parse HEAD) describe=\$(git
describe --always --tags)"
+
+ sed -i
'/<name>http.agent.name<\\/name>/,/<\\/property>/d' conf/nutch-site.xml.template
+ sed -i '/<\\/configuration>/i\\
+ <property>\\
+ <name>http.agent.name</name>\\
+ <value>Apache Nutch Single Node Smoke
Test</value>\\
+ </property>' conf/nutch-site.xml.template
+ ant runtime
+ """
+ }
+ }
+ }
+ stage('Start Hadoop Services') {
+ steps {
+ sh '''
+ echo "=== Hadoop configuration ==="
+ cat ${HADOOP_HOME}/etc/hadoop/core-site.xml
+ cat ${HADOOP_HOME}/etc/hadoop/hdfs-site.xml
+ cat ${HADOOP_HOME}/etc/hadoop/yarn-site.xml
+ cat ${HADOOP_HOME}/etc/hadoop/mapred-site.xml
+
+ echo "=== Allow DataNode to register in single-node/CI
(hostname check off) ==="
+ grep -q
'dfs.namenode.datanode.registration.ip-hostname-check'
${HADOOP_HOME}/etc/hadoop/hdfs-site.xml || sed -i '/<\\/configuration>/i\\
+ <property>\\
+ <name>dfs.namenode.datanode.registration.ip-hostname-check</name>\\
+ <value>false</value>\\
+ </property>' ${HADOOP_HOME}/etc/hadoop/hdfs-site.xml
+
+ echo "=== Formatting HDFS namenode ==="
+ hdfs namenode -format -force
+
+ echo "=== Clearing DataNode storage (clusterID must match
freshly formatted NameNode) ==="
+ rm -rf /tmp/hadoop-jenkins/dfs/data
+
+ echo "=== Starting HDFS daemons directly (no SSH) ==="
+ hdfs --daemon start namenode
+ hdfs --daemon start datanode
+
+ echo "=== Starting YARN daemons directly (no SSH) ==="
+ yarn --daemon start resourcemanager
+ yarn --daemon start nodemanager
+
+ echo "=== Waiting for daemons to initialize ==="
+ sleep 5
+
+ echo "=== Waiting for at least one HDFS DataNode to
register ==="
+ HDFS_WAIT_TIMEOUT=90
+ HDFS_WAIT_INTERVAL=3
+ elapsed=0
+ while [ $elapsed -lt $HDFS_WAIT_TIMEOUT ]; do
+ if hdfs dfsadmin -report 2>/dev/null | grep -qE "Live
datanodes \\([1-9][0-9]*\\)"; then
+ echo "HDFS is ready (DataNode registered after
${elapsed}s)."
+ break
+ fi
+ echo "Waiting for DataNode to register... (${elapsed}s /
${HDFS_WAIT_TIMEOUT}s)"
+ sleep $HDFS_WAIT_INTERVAL
+ elapsed=$((elapsed + HDFS_WAIT_INTERVAL))
+ done
+ if [ $elapsed -ge $HDFS_WAIT_TIMEOUT ]; then
+ echo "ERROR: No DataNode registered after
${HDFS_WAIT_TIMEOUT}s. HDFS report:"
+ hdfs dfsadmin -report || true
+ echo "=== DataNode process check ==="
+ jps -l 2>/dev/null | grep -i datanode || ps aux | grep
-i datanode | grep -v grep || true
+ echo "=== Last 200 lines of DataNode log ==="
+ ls -la ${HADOOP_HOME}/logs/*datanode*.log 2>/dev/null ||
true
+ tail -n 200 ${HADOOP_HOME}/logs/*datanode*.log
2>/dev/null || echo "No DataNode log found under ${HADOOP_HOME}/logs/"
+ echo "=== Last 100 lines of NameNode log ==="
+ tail -n 100 ${HADOOP_HOME}/logs/*namenode*.log
2>/dev/null || echo "No NameNode log found under ${HADOOP_HOME}/logs/"
+ exit 1
+ fi
+
+ echo "=== Verifying Hadoop daemons ==="
+ hdfs dfsadmin -report || echo "WARNING: HDFS report failed"
+ yarn node -list || echo "WARNING: YARN node list failed"
+ '''
+ }
+ }
+ stage('Smoke Test Nutch Core Tools') {
+ steps {
+ sh """
+ sed -i '/nutch index crawl\\/crawldb -linkdb
crawl\\/linkdb -dir crawl\\/segments/s/^/#/' test_nutch_tools.sh
+ sed -i '/nutch clean crawl\\/crawldb/s/^/#/'
test_nutch_tools.sh
+ ./test_nutch_tools.sh
+ """
+ }
+ }
+ stage('Stop Hadoop Services') {
+ steps {
+ sh '''
+ echo "=== Stopping YARN daemons ==="
+ yarn --daemon stop nodemanager || true
+ yarn --daemon stop resourcemanager || true
+
+ echo "=== Stopping HDFS daemons ==="
+ hdfs --daemon stop datanode || true
+ hdfs --daemon stop namenode || true
+ '''
+ }
+ }
+ }
+ post {
+ always {
+ script {
+ if (params.EMAIL_RECIPIENT != '') {
+ emailext (
+ to: params.EMAIL_RECIPIENT,
+ subject: "Jenkins Job ${env.JOB_NAME}
#${env.BUILD_NUMBER} - ${currentBuild.currentResult}",
+ body: """
+ Build ${currentBuild.currentResult}
+ Job: ${env.JOB_NAME}
+ Build Number: ${env.BUILD_NUMBER}
+ Git Ref: ${env.RESOLVED_GIT_REF ?: params.GIT_REF
?: 'master'}
+ Hadoop Version: ${params.HADOOP_VERSION}
+ Build URL: ${env.BUILD_URL}
+ """
+ )
+ }
+ }
+ }
+ }
+}