# ============================================================================
# Copyright and license info is available in the LICENSE file included with
# the Server Deployment Package (SDP), and also available online:
# https://swarm.workshop.perforce.com/projects/perforce-software-sdp/view/main/LICENSE
# ----------------------------------------------------------------------------
<#
.Synopsis
Various SDP related shared functions
.Description
Functions intended to be called by other scripts.
This script is not intended to be run directly.
$Id: //p4-sdp/dev/Server/Windows/p4/common/bin/SDP-functions.ps1#1 $
#>
Set-StrictMode -Version 2.0
# Try and avoid 2 byte output in log files!
$PSDefaultParameterValues['*:Encoding'] = 'utf8'
Function Parse-SDPConfigFile([string]$scriptpath) {
$SDPConfigFile = Split-Path -parent $scriptpath | Join-Path -childpath "..\..\config\sdp_config.ini"
$SDPConfigFile = Resolve-Path $SDPConfigFile
$ini = Parse-Inifile($SDPConfigFile)
$section = "${SDPInstance}:${env:computername}".tolower()
if ($ini[$section] -eq $null) {
throw @"
Section '$section' not found in ${SDPConfigFile} - did you specify a valid SDPInstance parameter
(before the ':') or are you on the wrong machine (hostname - is the value after the ':')?
"@
}
try {
$global:sdp_global_root = $ini[$section]["sdp_global_root"]
$global:sdp_serverid = $ini[$section]["sdp_serverid"]
$global:sdp_p4serviceuser = $ini[$section]["sdp_p4serviceuser"]
$global:sdp_p4superuser = $ini[$section]["sdp_p4superuser"]
$global:admin_pass_filename = $ini[$section]["admin_pass_filename"]
$global:email_pass_filename = $ini[$section]["email_pass_filename"]
$global:mailfrom = $ini[$section]["mailfrom"]
$global:maillist = $ini[$section]["maillist"]
$global:mailhost = $ini[$section]["mailhost"]
$global:mailhostport = $ini[$section]["mailhostport"]
$global:python = $ini[$section]["python"]
$global:remote_depotdata_root = $ini[$section]["remote_depotdata_root"]
$global:keepckps = 0
$global:keeplogs = 0
$result = [int32]::TryParse($ini[$section]["keepckps"], [ref]$global:keepckps)
$result = [int32]::TryParse($ini[$section]["keeplogs"], [ref]$global:keeplogs)
$global:limit_one_daily_checkpoint = $false
$value = $ini[$section]["limit_one_daily_checkpoint"]
if ($value -match "true|1|yes|y") {
$global:limit_one_daily_checkpoint = $true
}
$global:remote_sdp_instance = $ini[$section]["remote_sdp_instance"]
$global:p4target = $ini[$section]["p4target"]
}
catch {
$message = $_.Exception.GetBaseException().Message
Write-Error $message
throw "Error parsing ini file: section ${section}"
}
$global:SDP_INSTANCE_HOME = -join($global:SDP_GLOBAL_ROOT, "\p4\", $SDPInstance)
$global:SDP_INSTANCE_BIN_DIR = -join($global:SDP_INSTANCE_HOME, "\bin")
$global:P4exe = -join($global:SDP_INSTANCE_BIN_DIR, "\p4.exe")
$global:P4Dexe = -join($global:SDP_INSTANCE_BIN_DIR, "\p4d.exe")
$global:P4DVersion = "1970.0" # See set-vars below
Ensure-PathExists $global:p4exe
Ensure-PathExists $global:p4dexe
$global:SDP_INSTANCE_SSL_DIR = -join($global:SDP_INSTANCE_HOME, "\ssl")
$global:P4CONFIG = "p4config.txt"
$global:P4ROOT = -join($global:SDP_INSTANCE_HOME, "\root")
$global:P4JOURNAL = -join($global:SDP_INSTANCE_HOME, "\logs\journal")
$global:P4USER = $global:SDP_P4SUPERUSER
$env:P4TICKETS = -join($global:SDP_INSTANCE_HOME, "\P4Tickets.txt")
$env:P4TRUST = -join($global:SDP_INSTANCE_HOME, "\P4Trust.txt")
$env:P4ENVIRO = -join($global:SDP_INSTANCE_HOME, "\P4Enviro.txt")
$global:SDP_INSTANCE_P4SERVICE_NAME = -join("p4_", $SdpInstance)
$global:P4PORT = Get-P4PORT
$global:SCRIPTS_DIR = -join($global:SDP_GLOBAL_ROOT, "\p4\common\bin")
$global:SVCINST_EXE = -join($global:SCRIPTS_DIR, "\svcinst.exe")
$global:CONFIG_DIR = -join($global:SDP_GLOBAL_ROOT, "\p4\config")
$global:LOGS_DIR = -join($global:SDP_INSTANCE_HOME, "\logs")
$global:OFFLINE_DB_DIR = -join($global:SDP_INSTANCE_HOME, "\offline_db")
$global:CHECKPOINTS_DIR = -join($global:SDP_INSTANCE_HOME, "\checkpoints")
$global:REMOTE_CHECKPOINTS_DIR = -join($global:REMOTE_DEPOTDATA_ROOT, "\p4\", $global:REMOTE_SDP_INSTANCE, "\checkpoints")
$global:ADMIN_PASS_FILE = -join($global:CONFIG_DIR, "\", $global:ADMIN_PASS_FILENAME)
$global:EMAIL_PASS_FILE = -join($global:CONFIG_DIR, "\", $global:EMAIL_PASS_FILENAME)
$Global:IS_EDGESERVER = $false
$global:IS_STANDBYSERVER = $false
$global:IS_REPLICASERVER = $false
# The various Edge-specific tables for checkpoint/restore - used with -K/k parameter
# Note that this is p4d version specific - see set-vars
$global:EXCLUDED_EDGE_TABLES = "db.have,db.working,db.resolve,db.locks,db.revsh,db.workingx,db.resolvex"
$global:EDGE_CHECKPOINT_TABLES = "$global:EXCLUDED_EDGE_TABLES,db.view,db.label,db.revsx,db.revux"
$serverid_file = -join($global:P4ROOT, "\server.id")
$global:SERVERID = get-content $serverid_file
$global:P4LOG = -join($global:SDP_INSTANCE_HOME, "\logs\", $global:SERVERID, ".log")
# PATH=%SDP_INSTANCE_BIN_DIR%;%SCRIPTS_DIR%;%PATH%
Ensure-PathExists $global:SDP_INSTANCE_BIN_DIR
Ensure-PathExists $global:P4ROOT
Ensure-PathExists $global:LOGS_DIR
Ensure-PathExists $global:OFFLINE_DB_DIR
Ensure-PathExists $global:CHECKPOINTS_DIR
Ensure-PathExists $global:ADMIN_PASS_FILE
Set-vars
}
Function Set-vars {
$dbfile = "$global:P4ROOT\db.server"
if (!(Test-Path -path $dbfile)) {
return
}
# SERVERVAL from the server record bitwise anded with 4096 indicates an edge server.
# See https://www.perforce.com/perforce/doc.current/schema/#ServerServiceType
$serverval = & $p4dexe -r $global:P4ROOT -jd - db.server | select-string "@$global:SERVERID@" | %{$_.line.split()[8]}
if ($serverval -is [array]) {
$val = $serverval[0]
} else {
$val = $serverval
}
[int]$serverint = [convert]::toint32($val, 10)
if ($serverint -eq 0x19ED) {
$global:IS_EDGESERVER = $true
}
if (($serverint -eq 0x8945) -or ($serverint -eq 0x89E5)) {
$global:IS_STANDBYSERVER = $true
}
if (($serverint -eq 0x0945) -or ($serverint -eq 0x09E5) -or ($serverint -eq 0x094D)) {
$global:IS_REPLICASERVER = $true
}
$global:p4dversion = & $global:P4dexe -V | select-string "^Rev" | %{$_.line.split("/")[2]}
if ($global:p4dversion -gt "19.0") {
$global:EXCLUDED_EDGE_TABLES = "$global:EXCLUDED_EDGE_TABLES,db.stash,db.haveg,db.workingg,db.locksg,db.resolveg"
$global:EDGE_CHECKPOINT_TABLES = "$global:EXCLUDED_EDGE_TABLES,db.view,db.label,db.revsx,db.revux"
}
}
Function Write-Env-Var-File([string]$path) {
# Writes a file containing CMD env var statements
@"
set SDP_INSTANCE_HOME=$global:SDP_INSTANCE_HOME
set SDP_INSTANCE_BIN_DIR=$global:SDP_INSTANCE_BIN_DIR
set SDP_INSTANCE_SSL_DIR=$global:SDP_INSTANCE_SSL_DIR
set P4CONFIG=$global:P4CONFIG
set P4ROOT=$global:P4ROOT
set P4LOG=$global:P4LOG
set P4JOURNAL=$global:P4JOURNAL
set P4USER=$global:P4USER
set P4TICKETS=$env:P4TICKETS
set P4TRUST=$env:P4TRUST
set P4ENVIRO=$env:P4ENVIRO
set SDP_INSTANCE_P4SERVICE_NAME=$global:SDP_INSTANCE_P4SERVICE_NAME
set P4PORT=$global:P4PORT
set SCRIPTS_DIR=$global:SCRIPTS_DIR
set SVCINST_EXE=$global:SVCINST_EXE
set CONFIG_DIR=$global:CONFIG_DIR
set LOGS_DIR=$global:LOGS_DIR
set OFFLINE_DB_DIR=$global:OFFLINE_DB_DIR
set CHECKPOINTS_DIR=$global:CHECKPOINTS_DIR
set REMOTE_CHECKPOINTS_DIR=$global:REMOTE_CHECKPOINTS_DIR
set SERVERID=$global:SERVERID
"@ | set-content -path $path -encoding oem
}
Function Get-P4PORT {
$regkey = "HKLM:\SYSTEM\CurrentControlSet\services\${global:SDP_INSTANCE_P4SERVICE_NAME}\parameters"
(get-itemproperty $regkey).P4PORT
}
Function Get-Date-Time () {
Get-Date -format "yyyy\/MM\/dd HH\:mm\:ss"
}
Function Ensure-PathExists([string]$path) {
if (!(Test-Path -path $path)) {
throw "Error - required path $path doesn't exist!"
}
}
Function Create-LogFile () {
if (!$global:LogFileName) {
throw "ERROR - Global:LogFileName not set"
}
$global:LogFile = Join-Path $global:LOGS_DIR $global:LogFileName
Rotate-CurrentLogFile
write-debug "Using logfile: $global:logfile"
write-output "Starting ${global:ScriptTask}" | set-content -path $global:Logfile
}
Function Append-To-File([string]$message, [string]$filename) {
"$message" | add-content -path $filename -encoding UTF8
}
Function Log([string]$message) {
# Logs output to file and to console
$datetime = get-date-time
write-host "$datetime $message"
Append-To-File "$datetime $message" $global:Logfile
}
Function LogException([exception]$exception) {
$datetime = get-date-time
$message = $exception.message
write-error -message "$datetime $message" -Exception $exception
write-output "$datetime $message" | add-content -path $global:Logfile -encoding UTF8
}
Function Parse-IniFile ($file) {
$ini = @{}
# Create a default section if none exist in the file. Like a java prop file.
$section = "NO_SECTION"
$ini[$section] = @{}
switch -regex -file $file {
"^\[(.+)\]$" {
$section = $matches[1].Trim().ToLower()
$ini[$section] = @{}
}
"^\s*([^#].+?)\s*=\s*(.*)" {
$name,$value = $matches[1..2]
# skip comments that start with semicolon:
if (!($name.StartsWith(";"))) {
$ini[$section][$name] = $value.Trim()
}
}
}
$ini
}
Function Invoke-P4Login () {
Log "Logging in to P4 Instance"
# Due to the vagaries of powershell launching processes with redirection
# it is easier to spawn a cmd prompt.
$cmd = "$global:P4exe -p $global:P4PORT -u $global:SDP_P4SUPERUSER login -a < $global:ADMIN_PASS_FILE >> $global:LogFile 2>&1"
Log $cmd
cmd /c $cmd
if ($lastexitcode -ne 0) {
throw "ERROR - failed to login!"
}
}
Function Run-ReplicaStatus ([string]$statuslog) {
Log "Getting replica status"
$result = & $global:P4exe -p $global:P4PORT -u $global:SDP_P4SUPERUSER pull -lj 2>&1
Log $result
if ((Test-Path $statuslog -pathtype leaf)) {
remove-item $statuslog
}
Append-To-File "$result" $statuslog
if ($lastexitcode -ne 0) {
Log "WARNING - pull command exited with error code"
}
}
Function Check-OfflineDBExists () {
Log "Checking offline DB exists"
$path = Join-Path $global:OFFLINE_DB_DIR "db.counters"
Log "checking existence of $path"
if (!(Test-Path $path -pathtype leaf)) {
throw "ERROR - offline_db doesn't exist!"
}
}
Function Ensure-CheckpointNotRunning () {
Log "Ensuring there is no other checkpoint script running"
$checkFile = Join-Path $global:CHECKPOINTS_DIR "ckp_running.txt"
Log "checking existence of $checkFile"
if (Test-Path $checkFile -pathtype leaf) {
throw "Error - a checkpoint operation is already running! If in doubt please check with [email protected] for advice!! If you know that this is from a previous failure you may delete the file manually and re-run the checkpoint operation you were trying..."
}
write-output "Checkpoint running" | set-content -path $checkFile
}
Function Signal-CheckpointComplete () {
$checkFile = Join-Path $global:CHECKPOINTS_DIR "ckp_running.txt"
remove-item $checkFile
}
Function Get-CurrentJournalCounter () {
Log "Getting current live journal counter"
$cmd = "$global:P4exe -p $global:P4PORT -u $global:SDP_P4SUPERUSER counter journal"
$result = run-cmd-get-output $cmd "Error - failed to get live journal counter!"
[int]$global:JOURNAL_NUM = $result
[int]$global:CHECKPOINT_NUM = $global:JOURNAL_NUM + 1
Log "Live journal/checkpoint counters ${global:JOURNAL_NUM}/${global:CHECKPOINT_NUM}"
}
Function Get-JournalCounter ([string]$rootdir) {
$cmd = "$global:P4Dexe -r $rootdir -jd - db.counters"
Log $cmd
$rawjournal = Invoke-Expression $cmd | select-string "@journal@" | %{$_.line.split()[4]}
Log $rawjournal
$journal = [regex]::match($rawjournal, '^@([0-9]+)@$').Groups[1].Value
if ($lastexitcode -ne 0) {
throw "Error - failed to get $rootdir journal counter!"
}
return [int]$journal
}
Function Get-OfflineJournalCounter () {
Log "Getting offline journal counter"
$journal = Get-JournalCounter $global:OFFLINE_DB_DIR
Log "Offline journal counter $journal"
[int]$global:Offline_journal_num = $journal
}
Function Rotate-File ([string]$Rotate_file) {
if (Test-Path $Rotate_file -pathtype leaf) {
$suffix = (Get-ChildItem $Rotate_file).lastwritetime | Get-Date -format "yyyyMMdd\-HHmmss"
$newname = "$Rotate_file.$suffix"
Log "Rotating $Rotate_file to $newname"
rename-file "$Rotate_file" "$newname"
}
}
Function Rotate-CurrentLogFile () {
$Rotate_logname = $global:LOGFILE
if (Test-Path $Rotate_logname -pathtype leaf) {
$suffix = (Get-ChildItem $Rotate_logname).lastwritetime | Get-Date -format "yyyyMMdd\-HHmmss"
$newname = "$Rotate_logname.$suffix"
Log "Rotating $Rotate_logname to $newname"
rename-file "$Rotate_logname" "$newname"
}
}
Function Rename-File ([string]$old_name, [string]$new_name) {
# Retries frequently to cope with busy servers which may lock log files
$max_retries = 60 * 10 # 60 seconds at 10 times per second
$retries = $max_retries
$sleep_ms = 100
if (!(Test-Path $old_name -pathtype leaf)) {
return
}
While ($true) {
try {
# The -ea stop turns error into terminating error so it can be caught as exception
rename-item -path $old_name -newname $new_name -ea stop
return
}
catch {
if ($retries -eq 0) {
Log "Failed to rename file after $max_retries retries."
return
}
Start-Sleep -MilliSeconds $sleep_ms
$retries = $retries - 1
}
}
}
Function Rotate-LogFile () {
param([string]$Rotate_logname, [bool]$zip)
if (Test-Path $Rotate_logname -pathtype leaf) {
$newname = "$Rotate_logname.$global:logid"
Log "Rotating $Rotate_logname to $newname"
rename-file "$Rotate_logname" "$newname"
if ($zip) {
$gzip = -join($global:SCRIPTS_DIR, "\gzip.exe")
$cmd = "$gzip $newname"
run-cmd $cmd "failed to gzip file"
}
}
}
Function Rotate-Logfiles () {
Log "Rotating various logfiles"
$datetime = Get-Date -format "yyyyMMdd\-HHmmss"
$global:LOGID = "${global:JOURNAL_NUM}.$datetime"
Rotate-LogFile "${global:sdp_serverid}.log" $true
Rotate-LogFile "log" $true
Rotate-LogFile "p4broker.log" $true
Rotate-LogFile "audit.log" $true
Rotate-LogFile "sync_replica.log" $false
Rotate-LogFile "rotate-log-files.log" $false
Rotate-LogFile "create-filtered-edge-checkpoint.log" $false
Rotate-LogFile "recreate-offline-db-from-checkpoint.log" $false
Rotate-LogFile "recover-edge.log" $false
Rotate-LogFile "p4verify.log" $false
Rotate-LogFile "replica_status.log" $false
Rotate-LogFile "Weekly-sync-replica.log" $false
}
Function Move-Files ([string]$source, [string]$target) {
Get-ChildItem -Path $source |
foreach-object {
$f = $_.FullName
Log "Moving $f to $target"
move-item $f $target
}
}
Function Copy-Files ([string]$source, [string]$target) {
Get-ChildItem -Path $source |
foreach-object {
$f = $_.FullName
Log "Copying $f to $target"
copy-item $f $target
}
}
Function Remove-Files {
# Remove older files while keeping a specified minimum number
param([string]$remove_filename, [int]$keepnum)
$file_path = -join($remove_filename, "*")
$files = @(Get-ChildItem -Path $file_path | Sort-Object -Property LastWriteTime -Descending)
if ($files) {
for ($j = $keepnum; $j -lt $files.count; $j++) {
$f = $files[$j].FullName
Log("Removing: $f")
remove-item $f -Force
}
}
}
Function Remove-OldLogs () {
# Remove old Checkpoint Logs
# Use KEEPCKPS rather than KEEPLOGS, so we keep the same number
# of checkpoint logs as we keep checkpoints.
if ($global:keepckps -eq 0) {
Log "Skipping cleanup of old checkpoint logs because KEEPCKPS is set to 0."
} else {
log "Deleting old checkpoint logs. Keeping latest $global:KEEPCKPS, per KEEPCKPS setting in sdp_config.ini."
remove-files "checkpoint.log" $global:KEEPCKPS
remove-files $global:P4LOG $global:KEEPLOGS
remove-files "log" $global:KEEPLOGS
remove-files "p4broker.log" $global:KEEPLOGS
remove-files "audit.log" $global:KEEPLOGS
remove-files "sync_replica.log" $global:KEEPLOGS
remove-files "replica_status.log" $global:KEEPLOGS
remove-files "p4verify.log" $global:KEEPLOGS
remove-files "recreate_offline_db.log" $global:KEEPLOGS
remove-files "upgrade.log" $global:KEEPLOGS
remove-files "rotate-log-files.log" $global:KEEPLOGS
remove-files "create-filtered-edge-checkpoint.log" $global:KEEPLOGS
remove-files "recreate-offline-db-from-checkpoint.log" $global:KEEPLOGS
remove-files "recover-edge.log" $global:KEEPLOGS
remove-files "p4verify.log" $global:KEEPLOGS
remove-files "replica_status.log" $global:KEEPLOGS
remove-files "Weekly-sync-replica.log" $global:KEEPLOGS
}
}
Function Is-Replica () {
# If a replica then add -t for transfer to args
$target = & $global:P4exe -p $global:P4PORT -u $global:SDP_P4SUPERUSER configure show 2>&1 | select-string "P4TARGET="
Log $target
if ($lastexitcode -ne 0) {
return $false
}
if ($target -ne $null -and $target -ne "") {
return $true
}
return $false
}
Function Truncate-Journal () {
if ($global:IS_EDGESERVER) {
Log "[EDGE] Truncating live journal"
$checkpoint_path = -join($global:CHECKPOINTS_DIR, ".", $global:sdp_serverid, "\", $global:SDP_INSTANCE_P4SERVICE_NAME, ".", $global:sdp_serverid, ".ckp.", $global:Checkpoint_num, ".gz")
}
else {
Log "Truncating live journal"
$checkpoint_path = -join($global:CHECKPOINTS_DIR, "\", $global:SDP_INSTANCE_P4SERVICE_NAME, ".ckp.", $global:Checkpoint_num, ".gz")
}
if (Test-Path $checkpoint_path -pathtype leaf) {
#### HERE is edge
if ($global:IS_EDGESERVER) {
$checkFile = Join-Path $global:CHECKPOINTS_DIR "ckp_running.txt"
remove-item $checkFile
Log "[EDGE] Removed ckp_running.txt awaiting likely a journal rotation"
throw "Likely awaiting journal rotation"
}
else {
throw "ERROR - $checkpoint_path BUT ITS ELSE already exists, check the backup process"
}
}
if ($global:IS_EDGESERVER -or $global:IS_STANDBYSERVER -or $global:IS_REPLICASERVER) {
log "Skipping truncation of journal as this is not standard/commit server..."
} else {
# Specific path customization for edge servers
#if ($global:IS_EDGESERVER) {
# log "[EDGE] Truncating journal..."
# $journalprefix = -join($global:CHECKPOINTS_DIR, ".", $global:sdp_serverid, "\", $global:SDP_INSTANCE_P4SERVICE_NAME, ".", $global:sdp_serverid)
#} else {
$journalprefix = -join($global:CHECKPOINTS_DIR, "\", $global:SDP_INSTANCE_P4SERVICE_NAME)
#}
$journalpath = -join($journalprefix, ".jnl.", $global:JOURNAL_NUM)
if (Test-Path $journalpath -pathtype leaf) {
throw "ERROR - $journalpath already exists, 2check the backup process"
}
log "Truncating journal..."
# 'p4d -jj' does a copy-then-delete, instead of a simple mv.
# during 'p4d -jj' the perforce server will hang the responses to clients.
$cmd = "$global:P4Dexe -r $global:P4ROOT -J $global:P4JOURNAL -jj"
run-cmd $cmd "ERROR - attempting to truncate journal"
}
}
Function Run-Checkpoint () {
if ($global:IS_EDGESERVER) {
$checkpoint_prefix = -join($global:CHECKPOINTS_DIR, ".", $global:sdp_serverid, "\", $global:SDP_INSTANCE_P4SERVICE_NAME, ".", $global:sdp_serverid)
}
else {
$checkpoint_prefix = -join($global:CHECKPOINTS_DIR, "\", $global:SDP_INSTANCE_P4SERVICE_NAME)
}
$cmd = "$global:P4DEXE -r $global:P4ROOT -J $global:P4JOURNAL -jc -Z $checkpoint_prefix"
run-cmd $cmd "ERROR - attempting to checkpoint live database"
}
Function Replay-JournalsToOfflineDB () {
Log "Applying all outstanding journal files to offline_db"
# On Master/Commit we replay up to live journal, on edge/standby one less
$last_journal = $global:JOURNAL_NUM
# Adjust journal processing for edge/standby/replica
if ($global:IS_EDGESERVER -or $global:IS_STANDBYSERVER -or $global:IS_REPLICASERVER) {
$last_journal = $last_journal - 1
}
for ($j = $global:Offline_journal_num; $j -le $last_journal; $j++) {
# Specific path customization only for edge servers
if ($global:IS_EDGESERVER) {
Log "[EDGE] Replaying Journals To Offlinedb"
$journalprefix = -join($global:CHECKPOINTS_DIR, ".", $global:sdp_serverid, "\", $global:SDP_INSTANCE_P4SERVICE_NAME, ".", $global:sdp_serverid)
} else {
$journalprefix = -join($global:CHECKPOINTS_DIR, "\", $global:SDP_INSTANCE_P4SERVICE_NAME)
}
$journalpath = -join($journalprefix, ".jnl.", $j)
$cmd = "$global:P4DEXE -r $global:OFFLINE_DB_DIR -f -jr $journalpath"
run-cmd $cmd "ERROR - attempting to replay journal"
}
}
function Create-OfflineCheckpoint {
if ($global:IS_EDGESERVER) {
Log "[EDGE] Creating offline checkpoint"
$checkpoint_prefix = -join($global:CHECKPOINTS_DIR, ".", $global:sdp_serverid, "\", $global:SDP_INSTANCE_P4SERVICE_NAME, ".", $global:sdp_serverid, ".ckp.")
}
else {
Log "Creating offline checkpoint"
$checkpoint_prefix = -join($global:CHECKPOINTS_DIR, "\", $global:SDP_INSTANCE_P4SERVICE_NAME, ".ckp.")
}
if ($global:limit_one_daily_checkpoint -ne 0) {
$curr_date = Get-Date -format "yyyyMMdd"
$checkpoint_path = -join($checkpoint_prefix, "*.gz")
$files = @(Get-ChildItem -Path $checkpoint_path | Sort-Object -Property LastWriteTime -Descending)
if (!$files -or $files.count -eq 0) {
Log "LIMIT_ONE_DAILY_CHECKPOINT is set to true, but no checkpoint exists."
} else {
$ckp_date = $files[0].LastWriteTime | Get-Date -format "yyyyMMdd"
if ($curr_date -eq $ckp_date) {
Log "A checkpoint was already created today, and LIMIT_ONE_DAILY_CHECKPOINT is set to true."
Log "Skipping offline checkpoint dump."
return
}
}
}
$checkpoint_path = -join($checkpoint_prefix, $global:checkpoint_num, ".gz")
$cmd = "$global:P4DEXE -r $global:OFFLINE_DB_DIR -jd -z $checkpoint_path"
run-cmd $cmd "ERROR - attempting to create offline checkpoint"
}
Function Check-OfflineDBUsable () {
$offline_db_usable = -join($global:OFFLINE_DB_DIR, "\offline_db_usable.txt")
if (!(test-path $offline_db_usable -pathtype leaf)) {
$msg = "Offline database not in a usable state. Check the backup process."
Log $msg
throw $msg
}
}
Function Recreate-OfflineDBFiles () {
Log "Recreating offline db files"
$offline_db_usable = -join($global:OFFLINE_DB_DIR, "\offline_db_usable.txt")
if ($global:IS_EDGESERVER) {
$checkpoint_prefix = -join($global:CHECKPOINTS_DIR, ".", $global:sdp_serverid, "\", $global:SDP_INSTANCE_P4SERVICE_NAME, ".", $global:sdp_serverid, ".ckp.")
}
else {
$checkpoint_prefix = -join($global:CHECKPOINTS_DIR, "\", $global:SDP_INSTANCE_P4SERVICE_NAME, ".ckp.")
}
$checkpoint_path = -join($checkpoint_prefix, "*.gz")
$files = @(Get-ChildItem -Path $checkpoint_path | Sort-Object -Property LastWriteTime -Descending)
if (!$files -or $files.count -eq 0) {
$msg = "No checkpoints found - run live-checkpoint.ps1"
Log $msg
throw $msg
}
if (test-path $offline_db_usable -pathtype leaf) {
remove-item $offline_db_usable
}
remove-files "${global:OFFLINE_DB_DIR}\db." 0
Log "Recovering from latest checkpoint found"
$checkpoint_path = $files[0]
$cmd = "$global:P4DEXE -r $global:OFFLINE_DB_DIR -f -jr -z $checkpoint_path"
run-cmd $cmd "ERROR - attempting to recover offline db files"
"Offline db file restored successfully." | set-content -path $offline_db_usable
}
Function Remove-OldCheckpointsAndJournals () {
if ($global:keepckps -eq 0) {
Log "Skipping cleanup of old checkpoints because KEEPCKPS is set to 0."
} else {
log "Deleting obsolete checkpoints and journals. Keeping latest $global:KEEPCKPS, per KEEPCKPS setting in sdp_config.ini."
if ($global:IS_EDGESERVER) {
Log "[EDGE] Deleting"
$checkpoint_prefix = -join($global:CHECKPOINTS_DIR, ".", $global:sdp_serverid, "\", $global:SDP_INSTANCE_P4SERVICE_NAME, ".", $global:sdp_serverid, ".ckp.")
$journal_prefix = -join($global:CHECKPOINTS_DIR, ".", $global:sdp_serverid, "\", $global:SDP_INSTANCE_P4SERVICE_NAME, ".", $global:sdp_serverid, ".jnl.")
}
else {
Log "Deleting"
$checkpoint_prefix = -join($global:CHECKPOINTS_DIR, "\", $global:SDP_INSTANCE_P4SERVICE_NAME, ".ckp.")
$journal_prefix = -join($global:CHECKPOINTS_DIR, "\", $global:SDP_INSTANCE_P4SERVICE_NAME, ".jnl.")
}
# We multiply KEEPCKP by 2 for the ckp files because of the md5 files.
remove-files $checkpoint_prefix ($global:KEEPCKPS * 2)
remove-files $journal_prefix ($global:KEEPCKPS)
}
}
Function run-cmd-no-check ([string]$cmd) {
Log $cmd
$result = Invoke-Expression "$cmd >> $global:Logfile 2>&1"
Log $result
}
# This version returns the result
Function run-cmd-get-output ([string]$cmd, [string]$errmsg) {
Log $cmd
$result = Invoke-Expression $cmd
Log $result
if ($lastexitcode -ne 0) {
throw "$errmsg"
}
return $result
}
Function run-cmd ([string]$cmd, [string]$errmsg) {
Log $cmd
# Avoid NativeCommandError by preventing PS wrapping stderr output in error objects
Invoke-Expression $cmd >> $global:Logfile 2>&1 | %{ "$_" }
# Old version for posterity
# Invoke-Expression "$cmd >> $global:Logfile 2>&1"
if ($lastexitcode -ne 0) {
throw "$errmsg"
}
}
Function Log-DiskSpace () {
log "Checking disk space..."
$cmd = "$global:P4exe -p $global:P4PORT -u $global:SDP_P4SUPERUSER diskspace"
run-cmd "$cmd"
}
Function set-counter () {
Invoke-P4Login
$datetime = get-date-time
$cmd = "$global:P4exe -p $global:P4PORT -u $global:SDP_P4SUPERUSER counter lastSDPCheckpoint ""$datetime"""
run-cmd $cmd
}
Function Compare-JournalNumbers () {
# Ensures offline and live db counters are the same - avoids using out-of-date offline_db
$offlineJournal = Get-JournalCounter $global:OFFLINE_DB_DIR
$liveJournal = Get-JournalCounter $global:P4ROOT
if ($liveJournal -ne $offlineJournal) {
log "$global:P4ROOT journal number is: $liveJournal"
log "$global:OFFLINE_DB_DIR journal number is: $offlineJournal"
throw "$global:P4ROOT and $global:OFFLINE_DB_DIR journal numbers do not match."
}
}
Function Move-OfflineDBToLive () {
# Compare the Offline and Master journal numbers before switching to make sure they match.
Compare-JournalNumbers
log "Switching out db files..."
$savedir = "${global:P4ROOT}\save"
if (!(test-path -path $savedir -type container)) {
New-Item $savedir -ItemType directory
}
remove-files "$savedir\db." 0
move-files "${global:P4ROOT}\db.*" $savedir
move-files "${global:offline_db_dir}\db.*" $global:P4ROOT
remove-item "${global:offline_db_dir}\offline_db_usable.txt"
}
Function Test-IsAdmin {
([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")
}
Function Check-AdminPrivileges {
if (!(Test-IsAdmin)) {
throw "Please run this script with admin privileges"
}
}
Function Stop-LiveService () {
$cmd = "$global:SVCINST_EXE stop -n $global:SDP_INSTANCE_P4SERVICE_NAME"
run-cmd $cmd "Failed to stop Live-Service $global:SDP_INSTANCE_P4SERVICE_NAME"
}
Function Start-LiveService () {
$cmd = "$global:SVCINST_EXE start -n $global:SDP_INSTANCE_P4SERVICE_NAME"
run-cmd $cmd "Failed to start Live-Service $global:SDP_INSTANCE_P4SERVICE_NAME"
}
Function send-email ([string]$subject, [string]$logfilename) {
Log "Sending email with subject: $subject"
if ($logfilename) {
$contents = get-content $logfilename
} else {
$contents = get-content $global:logfile
}
$SMTPServer = $global:mailhost
$SMTPPort = "25"
if ($global:mailhostport -and $global:mailhostport -ne "") {
$SMTPPort = $global:mailhostport
}
try {
$email_password = ""
if ((test-path $global:email_pass_file -pathtype leaf)) {
Log "Found $global:email_pass_file"
$email_password = get-content $global:email_pass_file
}
$message = New-Object System.Net.Mail.MailMessage
$message.subject = $subject
$message.body = $contents -join "<br>`n"
$message.isbodyhtml = $true
$message.to.add($global:maillist)
$message.from = $global:mailfrom
$smtp = New-Object System.Net.Mail.SmtpClient($SMTPServer, $SMTPPort)
if ($SMTPPort -ne "25") {
$smtp.EnableSSL = $true
}
if ($email_password -ne "") {
$smtp.Credentials = New-Object System.Net.NetworkCredential($global:mailfrom, $email_password)
}
$smtp.send($message)
}
Catch
{
$ErrorMessage = $_.Exception.Message
Log "Failed to send email to ${global:mailhost}: ${ErrorMessage}"
}
}
| # | Change | User | Description | Committed | |
|---|---|---|---|---|---|
| #1 | 31397 | C. Thomas Tyler | Populate -b SDP_Classic_to_Streams -s //guest/perforce_software/sdp/...@31368. | ||
| //guest/perforce_software/sdp/dev/Server/Windows/p4/common/bin/SDP-functions.ps1 | |||||
| #39 | 30906 | Robert Cowham | Fix SDP-1137 issue with NativeCommandError (somewhat sprious error but worrying in logs!) | ||
| #38 | 30353 | Will Kreitzmann | Edge checkpoints | ||
| #37 | 28771 | C. Thomas Tyler |
Changed email address for Perforce Support. #review-28772 @amo @robert_cowham |
||
| #36 | 28089 | Robert Cowham | User serverid for p4log filename. | ||
| #35 | 27915 | Robert Cowham |
Implement p4login.bat properly with powershell. Fix to @27817 #review ttyler |
||
| #34 | 27722 | C. Thomas Tyler |
Refinements to @27712: * Resolved one out-of-date file (verify_sdp.sh). * Added missing adoc file for which HTML file had a change (WorkflowEnforcementTriggers.adoc). * Updated revdate/revnumber in *.adoc files. * Additional content updates in Server/Unix/p4/common/etc/cron.d/ReadMe.md. * Bumped version numbers on scripts with Version= def'n. * Generated HTML, PDF, and doc/gen files: - Most HTML and all PDF are generated using Makefiles that call an AsciiDoc utility. - HTML for Perl scripts is generated with pod2html. - doc/gen/*.man.txt files are generated with .../tools/gen_script_man_pages.sh. #review-27712 |
||
| #33 | 26658 | Robert Cowham | Make this file keytext for versioning | ||
| #32 | 26528 | Robert Cowham |
Start/stop services with svcinst now Refactor run-cmd to log command output for ease of support, e.g. when daily-checkpoint fails. |
||
| #31 | 26155 | C. Thomas Tyler |
Added '-f' flag to 'p4d -jr' command, for Windows SDP, to match similar change made previously in the Linux SDP. |
||
| #30 | 26151 | Robert Cowham | Make sure the rename works as expected and retries - was previously still not doing so. | ||
| #29 | 26120 | Robert Cowham |
Fix daily_checkpoint for edge/standby servers Make edge tables to include/exclude version specific Move password files to config dir |
||
| #28 | 26106 | Robert Cowham | Correct log message | ||
| #27 | 26104 | Robert Cowham | Add a retry loop for log renames (in case of server log being locked by busy server) | ||
| #26 | 26054 | Robert Cowham | Fix log typo | ||
| #25 | 25544 | C. Thomas Tyler |
Removed journalPrefix as command line paramter during journal rotation, deferring to db.config values, for Windows SDP. Removed explicit specification of journalPrefix as a command line argument to 'p4d -jj' command. Specifying the prefix is redundant as journalPrefix values are defined for the master server and any/all replicas/edge servers in db.config, and db.config is the One Source of Truth for journalPrefix. #review @robert_cowham @mshields |
||
| #24 | 22984 | Robert Cowham | Make sure rotate works | ||
| #23 | 22922 | Robert Cowham |
Add a utility to rotate log files - good as a scheduled task for replicas who may not have other jobs scheduled. |
||
| #22 | 22919 | Robert Cowham |
Refacto sdp-functions Convert upgrade.bat into upgrade.ps1 Fix issue with p4verify.ps1 |
||
| #21 | 22918 | Robert Cowham | Improve error message | ||
| #20 | 22809 | Robert Cowham | Fix problem when mailhostport not set in sdp_config.ini - now defaults properly to 25. | ||
| #19 | 22725 | Robert Cowham |
Remove unused parameter. Cope with multiple runs. |
||
| #18 | 22723 | Robert Cowham | New function used by recover-edge.ps1 | ||
| #17 | 20885 | Robert Cowham | Fix formatting of logs - when sent by gmail especially - use HTML with simple line breaks. | ||
| #16 | 20646 | Robert Cowham |
Got it working as far daily_backup tests. Refactor a few names. Auto wrap .ps1 commands. |
||
| #15 | 20624 | Robert Cowham |
Handle global:OFS properly if not set. Useful for array output. |
||
| #14 | 20595 | Robert Cowham |
New replica-status command Add corresponding function Improve end of line handling when logging |
||
| #13 | 20545 | Robert Cowham | Updated for sending emails via gmail accounts - known to work (if properly configured!) | ||
| #12 | 20537 | Robert Cowham | Update send-email to work with gmail accounts | ||
| #11 | 20296 | Robert Cowham | Avoid using write-output in log function so we don't have to be so careful with returns from get-journalCounter | ||
| #10 | 20293 | Robert Cowham |
Implement recreate-live-from-offline_db.ps1 - replacement fro weekly b_backup.bat which no longer needs to be run weekly. |
||
| #9 | 20204 | Robert Cowham | Fix checkpoint error - make sure we rotate correct journal. | ||
| #8 | 20186 | Robert Cowham |
Fix typo. Tidy up old server logs. |
||
| #7 | 20184 | Robert Cowham |
Added live-checkpoint.ps1 Fixed problem where all log files and others being removed - not keeping required number. |
||
| #6 | 20175 | Robert Cowham |
Set-strictmode Remove warnings Improve exception logging |
||
| #5 | 20150 | Robert Cowham | Refactored names to use PowerShell Verb-Noun convention | ||
| #4 | 20149 | Robert Cowham | Split rotation of current script log file from rotation of other log files. | ||
| #3 | 20148 | Robert Cowham | Added log info for functions | ||
| #2 | 20146 | Robert Cowham |
Refactor email sending. Output final message of success/failure. |
||
| #1 | 20142 | Robert Cowham | Initial versions of Powershell scripts | ||