#!/bin/bash # Copyright (c) Facebook, Inc. and its affiliates. # # This software may be used and distributed according to the terms of the # GNU General Public License version 2. # Library routines and initial setup for Mononoke-related tests. if [[ -n "$DB_SHARD_NAME" ]]; then MONONOKE_DEFAULT_START_TIMEOUT=60 else MONONOKE_DEFAULT_START_TIMEOUT=15 fi REPOID=0 REPONAME=repo COMMON_ARGS=(--skip-caching --mysql-master-only --tunables-config "file:$TESTTMP/mononoke_tunables.json") TEST_CERTDIR="${HGTEST_CERTDIR:-"$TEST_CERTS"}" function get_free_socket { # From https://unix.stackexchange.com/questions/55913/whats-the-easiest-way-to-find-an-unused-local-port cat > "$TESTTMP/get_free_socket.py" <> "$TESTTMP/mononoke.out" 2>&1 & export MONONOKE_PID=$! echo "$MONONOKE_PID" >> "$DAEMON_PIDS" } function mononoke_hg_sync { HG_REPO="$1" shift START_ID="$1" shift GLOG_minloglevel=5 "$MONONOKE_HG_SYNC" \ "${COMMON_ARGS[@]}" \ --retry-num 1 \ --repo-id $REPOID \ --mononoke-config-path mononoke-config \ --verify-server-bookmark-on-failure \ ssh://user@dummy/"$HG_REPO" "$@" sync-once --start-id "$START_ID" } function megarepo_tool { GLOG_minloglevel=5 "$MEGAREPO_TOOL" \ "${COMMON_ARGS[@]}" \ --repo-id $REPOID \ --mononoke-config-path mononoke-config \ "$@" } function megarepo_tool_multirepo { GLOG_minloglevel=5 "$MEGAREPO_TOOL" \ "${COMMON_ARGS[@]}" \ --mononoke-config-path mononoke-config \ "$@" } function mononoke_walker { GLOG_minloglevel=5 "$MONONOKE_WALKER" \ "${COMMON_ARGS[@]}" \ --repo-id $REPOID \ --mononoke-config-path mononoke-config \ "$@" } function mononoke_blobstore_healer { GLOG_minloglevel=5 "$MONONOKE_BLOBSTORE_HEALER" \ "${COMMON_ARGS[@]}" \ --mononoke-config-path mononoke-config \ "$@" 2>&1 | grep -v "Could not connect to a replica" } function mononoke_x_repo_sync() { source_repo_id=$1 target_repo_id=$2 shift shift GLOG_minloglevel=5 "$MONONOKE_X_REPO_SYNC" \ "${COMMON_ARGS[@]}" \ --mononoke-config-path "$TESTTMP/mononoke-config" \ --source-repo-id "$source_repo_id" \ --target-repo-id "$target_repo_id" \ "$@" } function mononoke_rechunker { GLOG_minloglevel=5 "$MONONOKE_RECHUNKER" \ "${COMMON_ARGS[@]}" \ --repo-id $REPOID \ --mononoke-config-path mononoke-config \ "$@" } function mononoke_hg_sync_with_retry { GLOG_minloglevel=5 "$MONONOKE_HG_SYNC" \ "${COMMON_ARGS[@]}" \ --base-retry-delay-ms 1 \ --repo-id $REPOID \ --mononoke-config-path mononoke-config \ --verify-server-bookmark-on-failure \ ssh://user@dummy/"$1" sync-once --start-id "$2" } function mononoke_hg_sync_with_failure_handler { sql_name="${TESTTMP}/hgrepos/repo_lock" GLOG_minloglevel=5 "$MONONOKE_HG_SYNC" \ "${COMMON_ARGS[@]}" \ --retry-num 1 \ --repo-id $REPOID \ --mononoke-config-path mononoke-config \ --verify-server-bookmark-on-failure \ --lock-on-failure \ --repo-lock-sqlite \ --repo-lock-db-address "$sql_name" \ ssh://user@dummy/"$1" sync-once --start-id "$2" } function create_repo_lock_sqlite3_db { cat >> "$TESTTMP"/repo_lock.sql <> "$TESTTMP"/mutable_counters.sql <> "$TESTTMP"/bookmarks.sql <&2 jq -S . < "$file" >&2 return 1 } # Wait until a Mononoke server is available for this repo. function wait_for_mononoke { # MONONOKE_START_TIMEOUT is set in seconds # Number of attempts is timeout multiplied by 10, since we # sleep every 0.1 seconds. local attempts timeout timeout="${MONONOKE_START_TIMEOUT:-"$MONONOKE_DEFAULT_START_TIMEOUT"}" attempts="$((timeout * 10))" SSLCURL="sslcurl --noproxy localhost \ https://localhost:$MONONOKE_SOCKET" for _ in $(seq 1 $attempts); do $SSLCURL 2>&1 | grep -q 'Empty reply' && break sleep 0.1 done if ! $SSLCURL 2>&1 | grep -q 'Empty reply'; then echo "Mononoke did not start" >&2 cat "$TESTTMP/mononoke.out" exit 1 fi } # Wait until cache warmup finishes function wait_for_mononoke_cache_warmup { local attempts=150 for _ in $(seq 1 $attempts); do grep -q "finished initial warmup" "$TESTTMP/mononoke.out" && break sleep 0.1 done if ! grep -q "finished initial warmup" "$TESTTMP/mononoke.out"; then echo "Mononoke warmup did not finished" >&2 cat "$TESTTMP/mononoke.out" exit 1 fi } function setup_common_hg_configs { cat >> "$HGRCPATH" <> "$TESTTMP"/pushrebaserecording.sql <&1 exit 1 fi if [[ ! -e "$TESTTMP/mononoke_hgcli" ]]; then local priority="" if [[ -n "${MONONOKE_HGCLI_PRIORITY:-}" ]]; then priority="--priority $MONONOKE_HGCLI_PRIORITY" fi cat >> "$TESTTMP/mononoke_hgcli" < "$TESTTMP/mononoke_tunables.json" ALLOWED_USERNAME="${ALLOWED_USERNAME:-myusername0}" cd mononoke-config || exit 1 mkdir -p common touch common/commitsyncmap.toml cat > common/common.toml < common/storage.toml setup_mononoke_storage_config "$REPOTYPE" "$blobstorename" setup_mononoke_repo_config "$REPONAME" "$blobstorename" } function db_config() { local blobstorename="$1" if [[ -n "$DB_SHARD_NAME" ]]; then echo "[$blobstorename.metadata.remote]" echo "primary = { db_address = \"$DB_SHARD_NAME\" }" echo "filenodes = { unsharded = { db_address = \"$DB_SHARD_NAME\" } }" echo "mutation = { db_address = \"$DB_SHARD_NAME\" }" else echo "[$blobstorename.metadata.local]" echo "local_db_path = \"$TESTTMP/monsql\"" fi } function blobstore_db_config() { if [[ -n "$DB_SHARD_NAME" ]]; then echo "queue_db = { remote = { db_address = \"$DB_SHARD_NAME\" } }" else local blobstore_db_path="$TESTTMP/blobstore_sync_queue" mkdir -p "$blobstore_db_path" echo "queue_db = { local = { local_db_path = \"$blobstore_db_path\" } }" fi } function setup_mononoke_storage_config { local underlyingstorage="$1" local blobstorename="$2" local blobstorepath="$TESTTMP/$blobstorename" if [[ -v MULTIPLEXED ]]; then cat >> common/storage.toml <> common/storage.toml else echo " { blobstore_id = $i, blobstore = { $underlyingstorage = { path = \"$blobstorepath/$i\" } } }," >> common/storage.toml fi done echo ']' >> common/storage.toml else mkdir -p "$blobstorepath" cat >> common/storage.toml <> "$LOADSHED_CONF/limits" <> "$PUSHREDIRECT_CONF/enable" < "repos/$reponame/server.toml" <> "repos/$reponame/server.toml" <> "repos/$reponame/server.toml" <> "repos/$reponame/server.toml" <> "repos/$reponame/server.toml" <> "repos/$reponame/server.toml" <> "repos/$reponame/server.toml" <> "repos/$reponame/server.toml" <> "repos/$reponame/server.toml" <> "repos/$reponame/server.toml" <> "repos/$reponame/server.toml" <> "repos/$reponame/server.toml" <> "repos/$reponame/server.toml" <> "repos/$reponame/server.toml" <> "repos/$reponame/server.toml" <> "repos/$reponame/server.toml" <> "repos/$reponame/server.toml" <> "repos/$reponame/server.toml" <> "repos/$reponame/server.toml" <> "repos/$reponame/server.toml" <> "repos/$reponame/server.toml" <> "repos/$reponame/server.toml" <> "repos/$reponame/server.toml" <> "repos/$reponame/server.toml" <> "repos/$reponame/server.toml" <> "repos/$reponame/server.toml" <> "repos/$reponame/server.toml" <> "repos/$reponame/server.toml" <> "repos/$reponame/server.toml" <> "repos/$reponame/server.toml" <> "repos/$reponame/server.toml" <> "repos/$reponame/server.toml" <> "repos/$reponame/server.toml" <> "repos/$REPONAME/server.toml" } function backfill_git_mapping { GLOG_minloglevel=5 "$MONONOKE_BACKFILL_GIT_MAPPING" --repo_id "$REPOID" \ --mononoke-config-path "$TESTTMP/mononoke-config" "${COMMON_ARGS[@]}" "$@" } function blobimport { local always_log= if [[ "$1" == "--log" ]]; then always_log=1 shift fi input="$1" output="$2" shift 2 mkdir -p "$output" $MONONOKE_BLOBIMPORT --repo-id $REPOID \ --mononoke-config-path "$TESTTMP/mononoke-config" \ "$input" "${COMMON_ARGS[@]}" "$@" > "$TESTTMP/blobimport.out" 2>&1 BLOBIMPORT_RC="$?" if [[ $BLOBIMPORT_RC -ne 0 ]]; then cat "$TESTTMP/blobimport.out" # set exit code, otherwise previous cat sets it to 0 return "$BLOBIMPORT_RC" elif [[ -n "$always_log" ]]; then cat "$TESTTMP/blobimport.out" fi } function bonsai_verify { GLOG_minloglevel=5 "$MONONOKE_BONSAI_VERIFY" --repo-id "$REPOID" \ --mononoke-config-path "$TESTTMP/mononoke-config" "${COMMON_ARGS[@]}" "$@" } function lfs_import { GLOG_minloglevel=5 "$MONONOKE_LFS_IMPORT" --repo-id "$REPOID" \ --mononoke-config-path "$TESTTMP/mononoke-config" "${COMMON_ARGS[@]}" "$@" } function s_client { /usr/local/fbcode/platform007/bin/openssl s_client \ -connect localhost:$MONONOKE_SOCKET \ -CAfile "${TEST_CERTDIR}/root-ca.crt" \ -cert "${TEST_CERTDIR}/localhost.crt" \ -key "${TEST_CERTDIR}/localhost.key" \ -ign_eof "$@" } function start_and_wait_for_scs_server { export SCS_PORT SCS_PORT=$(get_free_socket) GLOG_minloglevel=5 "$SCS_SERVER" "$@" \ -p "$SCS_PORT" \ --mononoke-config-path "$TESTTMP/mononoke-config" \ "${COMMON_ARGS[@]}" >> "$TESTTMP/scs_server.out" 2>&1 & export SCS_SERVER_PID=$! echo "$SCS_SERVER_PID" >> "$DAEMON_PIDS" # Wait until a SCS server is available # MONONOKE_START_TIMEOUT is set in seconds # Number of attempts is timeout multiplied by 10, since we # sleep every 0.1 seconds. local attempts timeout timeout="${MONONOKE_START_TIMEOUT:-"$MONONOKE_DEFAULT_START_TIMEOUT"}" attempts="$((timeout * 10))" CHECK_SSL="/usr/local/fbcode/platform007/bin/openssl s_client -connect localhost:$SCS_PORT" for _ in $(seq 1 $attempts); do $CHECK_SSL 2>&1 &1 &2 cat "$TESTTMP/scs_server.out" exit 1 fi } function scsc { GLOG_minloglevel=5 "$SCS_CLIENT" --host "localhost:$SCS_PORT" "$@" } function start_edenapi_server { local port log attempts timeout port=$(get_free_socket) log="$TESTTMP/edenapi_server.out" # Start the EdenAPI server, using test TLS credentials. # This means that tests must use TLS to connect to the # server. The `sslcurl` function should help with this. GLOG_minloglevel=5 "$EDENAPI_SERVER" "$@" \ --debug \ --listen-host 127.0.0.1 \ --listen-port "$port" \ --mononoke-config-path "$TESTTMP/mononoke-config" \ --tls-ca "$TEST_CERTDIR/root-ca.crt" \ --tls-private-key "$TEST_CERTDIR/localhost.key" \ --tls-certificate "$TEST_CERTDIR/localhost.crt" \ --tls-ticket-seeds "$TEST_CERTDIR/server.pem.seeds" \ --trusted-proxy-identity USER:myusername0 \ "${COMMON_ARGS[@]}" >> "$log" 2>&1 & # Record the PID of the spawned process so the test framework # can kill it later during cleanup. echo "$!" >> "$DAEMON_PIDS" # Export the URI that tests will use to connect to the server. export EDENAPI_URI="https://localhost:$port" # Wait for the server to start listening for HTTP requests. timeout="${MONONOKE_START_TIMEOUT:-"$MONONOKE_DEFAULT_START_TIMEOUT"}" attempts="$((timeout * 10))" for _ in $(seq 1 $attempts); do if sslcurl -q "$EDENAPI_URI/health_check" > /dev/null 2>&1; then truncate -s 0 "$log" return 0 fi sleep 0.1 done echo "EdenAPI server failed to start" >&2 cat "$log" >&2 return 1 } function edenapi_make_req { "$EDENAPI_MAKE_REQ" "$@" } function edenapi_read_res { "$EDENAPI_READ_RES" "$@" } function lfs_server { local port uri log opts args proto poll lfs_server_pid port="$(get_free_socket)" log="${TESTTMP}/lfs_server.${port}" proto="http" poll="curl" opts=( "${COMMON_ARGS[@]}" --mononoke-config-path "$TESTTMP/mononoke-config" --listen-host 127.0.0.1 --listen-port "$port" --test-friendly-logging ) args=() while [[ "$#" -gt 0 ]]; do if [[ "$1" = "--upstream" ]]; then shift args=("${args[@]}" "$1") shift elif [[ "$1" = "--live-config" ]]; then opts=("${opts[@]}" "$1" "$2" "--live-config-fetch-interval" "1") shift shift elif [[ "$1" = "--tls" ]]; then proto="https" poll="sslcurl" opts=( "${opts[@]}" --tls-ca "$TEST_CERTDIR/root-ca.crt" --tls-private-key "$TEST_CERTDIR/localhost.key" --tls-certificate "$TEST_CERTDIR/localhost.crt" --tls-ticket-seeds "$TEST_CERTDIR/server.pem.seeds" ) shift elif [[ "$1" = "--always-wait-for-upstream" ]]; then opts=("${opts[@]}" "$1") shift elif [[ "$1" = "--allowed-test-identity" ]] || [[ "$1" = "--scuba-log-file" ]] || [[ "$1" = "--trusted-proxy-identity" ]] || [[ "$1" = "--max-upload-size" ]] then opts=("${opts[@]}" "$1" "$2") shift shift elif [[ "$1" = "--log" ]]; then shift log="$1" shift else echo "invalid argument: $1" >&2 return 1 fi done uri="${proto}://localhost:${port}" echo "$uri" GLOG_minloglevel=5 "$LFS_SERVER" \ "${opts[@]}" "$uri" "${args[@]}" >> "$log" 2>&1 & lfs_server_pid="$!" echo "$lfs_server_pid" >> "$DAEMON_PIDS" for _ in $(seq 1 200); do if "$poll" "${uri}/health_check" >/dev/null 2>&1; then truncate -s 0 "$log" return 0 fi sleep 0.1 done echo "lfs_server did not start:" >&2 cat "$log" >&2 return 1 } function extract_json_error { input=$(< /dev/stdin) echo "$input" | head -1 | jq -r '.message' echo "$input" | tail -n +2 } # Run an hg binary configured with the settings required to talk to Mononoke. function hgmn { hg --config ui.ssh="$DUMMYSSH" --config paths.default=ssh://user@dummy/$REPONAME --config ui.remotecmd="$MONONOKE_HGCLI" "$@" } function hgmn_local { hg --config ui.ssh="${TEST_FIXTURES}/nossh.sh" "$@" } function hgmn_show { echo "LOG $*" hgmn log --template 'node:\t{node}\np1node:\t{p1node}\np2node:\t{p2node}\nauthor:\t{author}\ndate:\t{date}\ndesc:\t{desc}\n\n{diff()}' -r "$@" hgmn update "$@" echo echo "CONTENT $*" find . -type f -not -path "./.hg/*" -print -exec cat {} \; } function hginit_treemanifest() { hg init "$@" cat >> "$1"/.hg/hgrc <> "$2"/.hg/hgrc <> "$1"/.hg/hgrc <> "$2"/.hg/hgrc <> .hg/hgrc <> .hg/hgrc <> .hg/hgrc <> "repos/$REPONAME/server.toml" <> "$HGRCPATH" <> .hg/hgrc <> .hg/hgrc <> .hg/hgrc < "$1" hg add "$1" hg ci -m "$1" } function call_with_certs() { REPLAY_CA_PEM="$TEST_CERTDIR/root-ca.crt" \ THRIFT_TLS_CL_CERT_PATH="$TEST_CERTDIR/localhost.crt" \ THRIFT_TLS_CL_KEY_PATH="$TEST_CERTDIR/localhost.key" \ GLOG_minloglevel=5 "$@" } function traffic_replay() { call_with_certs "$TRAFFIC_REPLAY" \ --loglevel warn \ --testrun \ --hgcli "$MONONOKE_HGCLI" \ --mononoke-address "[::1]:$MONONOKE_SOCKET" \ --mononoke-server-common-name localhost } function enable_replay_verification_hook { cat >> "$TESTTMP"/replayverification.py < hg sync job we need a way to # quickly disable the replay verification to let unsynced bundles # through. # Disable this hook by placing a file in the .hg directory. if repo.localvfs.exists('REPLAY_BYPASS'): ui.note("[ReplayVerification] Bypassing check as override file is present\n") return 0 if expected_book is None and expected_head is None: # We are allowing non-unbundle-replay pushes to go through return 0 if allowed_replay_books and actual_book not in allowed_replay_books: ui.warn("[ReplayVerification] only allowed to unbundlereplay on %r\n" % (allowed_replay_books, )) return 1 expected_head = expected_head or None actual_head = actual_head or None if expected_book == actual_book and expected_head == actual_head: ui.note("[ReplayVerification] Everything seems in order\n") return 0 ui.warn("[ReplayVerification] Expected: (%s, %s). Actual: (%s, %s)\n" % (expected_book, expected_head, actual_book, actual_head)) return 1 EOF cat >> "$TESTTMP"/repo_lock.py << EOF def run(*args, **kwargs): """Repo is locked for everything except replays In-process style hook.""" if kwargs.get("EXPECTED_ONTOBOOK"): return 0 print "[RepoLock] Repo locked for non-unbundlereplay pushes" return 1 EOF [[ -f .hg/hgrc ]] || echo ".hg/hgrc does not exists!" cat >>.hg/hgrc </dev/null | cut -d' ' -f2 export REPOID="$repoid_backup" } function create_replaybookmarks_table() { if [[ -n "$DB_SHARD_NAME" ]]; then # We don't need to do anything: the MySQL setup creates this for us. true else # We don't actually create any DB here, replaybookmarks will create it for it # when it opens a SQLite DB in this directory. mkdir "$TESTTMP/replaybookmarksqueue" fi } function insert_replaybookmarks_entry() { local repo bookmark repo="$1" bookmark="$2" node="$3" if [[ -n "$DB_SHARD_NAME" ]]; then # See above for why we have to redirect this output to /dev/null db -w "$DB_SHARD_NAME" 2>/dev/null < /dev/null | grep -v COUNT else local attempts timeout ret timeout="100" attempts="$((timeout * 10))" for _ in $(seq 1 $attempts); do ret="$(sqlite3 "$TESTTMP/blobstore_sync_queue/sqlite_dbs" "select count(*) from blobstore_sync_queue" 2>/dev/null)" if [[ -n "$ret" ]]; then echo "$ret" return 0 fi sleep 0.1 done return 1 fi } function erase_blobstore_sync_queue() { if [[ -n "$DB_SHARD_NAME" ]]; then # See above for why we have to redirect this output to /dev/null db -wu "$DB_SHARD_NAME" 2> /dev/null <> "$HGRCPATH" <> .hg/hgrc <"$log" 2>&1 ret="$?" if [[ "$ret" == 0 ]]; then return "$ret" fi cat "$log" return "$ret" } function commitcloud_fill_one() { # Note: do not use `call_with_certs` here or # put cert paths into env vars, as this causes # the binary to lose access to `getdb.sh`-acquired # database local CERTDIR="${HGTEST_CERTDIR:-"$TEST_CERTS"}" "$MONONOKE_COMMITCLOUD_FILLONE" \ hg-to-mononoke \ --hgcli "$MONONOKE_HGCLI" \ --mononoke-address "[::1]:$MONONOKE_SOCKET" \ --mononoke-server-common-name localhost \ --cert-override "$CERTDIR/localhost.crt" \ --private-key-override "$CERTDIR/localhost.key" \ --ca-pem-override "$CERTDIR/root-ca.crt" \ --reponame $REPONAME \ --rebundler-path "$MONONOKE_COMMITCLOUD_INFINITEPUSHREBUNDLE/infinitepushrebundle.py" \ --debug "$@" } function commitcloud_reverse_fill_one() { "$MONONOKE_COMMITCLOUD_FILLONE" \ mononoke-to-hg \ --reponame $REPONAME \ --rebundler-path "$MONONOKE_COMMITCLOUD_INFINITEPUSHREBUNDLE/infinitepushrebundle.py" \ --debug "$@" } function commitcloud_reversefiller_iteration() { local default_sqlite_db="$TESTTMP/monsql/sqlite_dbs" local sqlitedb="${REVERSEFILLER_SQLITE_DB:-"$default_sqlite_db"}" "$MONONOKE_COMMITCLOUD_REVERSEFILLER" \ --num-workers 1 \ --identity "testfiller" \ --reponame "$REPONAME" \ --rebundler-path "$MONONOKE_COMMITCLOUD_INFINITEPUSHREBUNDLE/infinitepushrebundle.py" \ --stop-after-n-iterations=10 \ --with-sqlite-db="$sqlitedb" \ --debug "$@" } function commitcloud_forwardfiller_iteration() { # Note: do not use `call_with_certs` here or # put cert paths into env vars, as this causes # the binary to lose access to `getdb.sh`-acquired # database local CERTDIR="${HGTEST_CERTDIR:-"$TEST_CERTS"}" "$MONONOKE_COMMITCLOUD_FORWARDFILLER" \ --num-workers 1 \ --hgcli "$MONONOKE_HGCLI" \ --mononoke-address "[::1]:$MONONOKE_SOCKET" \ --mononoke-server-common-name localhost \ --cert-override "$CERTDIR/localhost.crt" \ --private-key-override "$CERTDIR/localhost.key" \ --ca-pem-override "$CERTDIR/root-ca.crt" \ --identity "testfiller" \ --reponame "$REPONAME" \ --rebundler-path "$MONONOKE_COMMITCLOUD_INFINITEPUSHREBUNDLE/infinitepushrebundle.py" \ --stop-after-n-iterations=10 \ --debug "$@" } function repo_import() { log="$TESTTMP/repo_import.out" "$MONONOKE_REPO_IMPORT" \ "${COMMON_ARGS[@]}" \ --repo-id "$REPOID" \ --mononoke-config-path "${TESTTMP}/mononoke-config" \ "$@" }