blob: d9af6e6992955ab0119b01980aeb5a983913075c [file] [log] [blame]
#
# 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.
#-----------------------------------------------------------------------------
Function PrintUsage
{
echo @"
usage: cassandra.ps1 [-f] [-h] [-p pidfile] [-H dumpfile] [-D arg] [-E errorfile] [-install | -uninstall] [-help]
-f Run cassandra in foreground
-install install cassandra as a service
-uninstall remove cassandra service
-p pidfile tracked by server and removed on close (defaults to pid.txt)
-H change JVM HeapDumpPath
-D items to append to JVM_OPTS
-E change JVM ErrorFile
-v Print cassandra version and exit
-s Show detailed jvm environment information during launch
-help print this message
NOTE: installing cassandra as a service requires Commons Daemon Service Runner
available at http://commons.apache.org/proper/commons-daemon/"
"@
exit
}
#-----------------------------------------------------------------------------
# Note: throughout these scripts we're replacing \ with /. This allows clean
# operation on both command-prompt and cygwin-based environments.
Function Main
{
ValidateArguments
# support direct run of .ps1 file w/out batch file
if ($env:CASSANDRA_HOME -eq $null)
{
$scriptDir = Split-Path $script:MyInvocation.MyCommand.Path
$env:CASSANDRA_HOME = (Get-Item $scriptDir).parent.FullName
}
. "$env:CASSANDRA_HOME\bin\source-conf.ps1"
$conf = Find-Conf
if ($s)
{
echo "Sourcing cassandra config file: $conf"
}
. $conf
SetCassandraEnvironment
if ($v)
{
PrintVersion
exit
}
$pidfile = "$env:CASSANDRA_HOME\$pidfile"
# Other command line params
if ($H)
{
$env:JVM_OPTS = $env:JVM_OPTS + " -XX:HeapDumpPath=$H"
}
if ($E)
{
$env:JVM_OPTS = $env:JVM_OPTS + " -XX:ErrorFile=$E"
}
if ($p)
{
$pidfile = "$p"
$env:CASSANDRA_PARAMS = $env:CASSANDRA_PARAMS + ' -Dcassandra-pidfile="' + "$pidfile" + '"'
}
# Parse -D and -X JVM_OPTS
for ($i = 0; $i -lt $script:args.Length; ++$i)
{
if ($script:args[$i].StartsWith("-D") -Or $script:args[$i].StartsWith("-X"))
{
$env:JVM_OPTS = "$env:JVM_OPTS " + $script:args[$i]
}
}
if ($install -or $uninstall)
{
HandleInstallation
}
else
{
VerifyPortsAreAvailable
RunCassandra($f)
}
}
#-----------------------------------------------------------------------------
Function HandleInstallation
{
$SERVICE_JVM = """cassandra"""
$PATH_PRUNSRV = "$env:CASSANDRA_HOME\bin\daemon"
$PR_LOGPATH = $serverPath
if (-Not (Test-Path $PATH_PRUNSRV\prunsrv.exe))
{
Write-Warning "Cannot find $PATH_PRUNSRV\prunsrv.exe. Please download package from http://www.apache.org/dist/commons/daemon/binaries/windows/ to install as a service."
Break
}
If (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator"))
{
Write-Warning "Cannot perform installation without admin credentials. Please re-run as administrator."
Break
}
if (!$env:PRUNSRV)
{
$env:PRUNSRV="$PATH_PRUNSRV\prunsrv"
}
$regPath = "HKLM:\SYSTEM\CurrentControlSet\services\Tcpip\Parameters\"
echo "Attempting to delete existing $SERVICE_JVM service..."
Start-Sleep -s 2
$proc = Start-Process -FilePath "$env:PRUNSRV" -ArgumentList "//DS//$SERVICE_JVM" -PassThru -WindowStyle Hidden
echo "Reverting to default TCP keepalive settings (2 hour timeout)"
Remove-ItemProperty -Path $regPath -Name KeepAliveTime -EA SilentlyContinue
# Quit out if this is uninstall only
if ($uninstall)
{
return
}
echo "Installing [$SERVICE_JVM]."
Start-Sleep -s 2
$proc = Start-Process -FilePath "$env:PRUNSRV" -ArgumentList "//IS//$SERVICE_JVM" -PassThru -WindowStyle Hidden
echo "Setting launch parameters for [$SERVICE_JVM]"
Start-Sleep -s 2
$args = @"
//US//$SERVICE_JVM
--Jvm=auto --StdOutput auto --StdError auto
--Classpath=$env:CLASSPATH
--StartMode=jvm --StartClass=$env:CASSANDRA_MAIN --StartMethod=main
--StopMode=jvm --StopClass=$env:CASSANDRA_MAIN --StopMethod=stop
--PidFile "$pidfile"
"@
# Include cassandra params
$prunArgs = "$env:CASSANDRA_PARAMS $env:JVM_OPTS"
# Change to semicolon delim as we can't split on space due to potential spaces in directory names
$prunArgs = $prunArgs -replace " -", ";-"
# JvmOptions w/multiple semicolon delimited items isn't working correctly. storagedir and logdir were
# both being ignored / failing to parse on startup. See CASSANDRA-8115
$split_opts = $prunArgs.Split(";")
foreach ($arg in $split_opts)
{
$args += " ++JvmOptions=$arg"
}
$args = $args -replace [Environment]::NewLine, ""
$proc = Start-Process -FilePath "$env:PRUNSRV" -ArgumentList $args -PassThru -WindowStyle Hidden
echo "Setting KeepAliveTimer to 5 minutes for TCP keepalive"
Set-ItemProperty -Path $regPath -Name KeepAliveTime -Value 300000
echo "Installation of [$SERVICE_JVM] is complete"
}
#-----------------------------------------------------------------------------
Function PrintVersion()
{
Write-Host "Cassandra Version: " -NoNewLine
$pinfo = New-Object System.Diagnostics.ProcessStartInfo
$pinfo.FileName = "$env:JAVA_BIN"
$pinfo.UseShellExecute = $false
$pinfo.Arguments = "-Dlogback.configurationFile=logback-tools.xml -cp $env:CLASSPATH org.apache.cassandra.tools.GetVersion"
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $pinfo
$p.Start() | Out-Null
$p.WaitForExit()
}
#-----------------------------------------------------------------------------
Function RunCassandra([string]$foreground)
{
echo "Starting cassandra server"
$cmd = @"
$env:JAVA_BIN
"@
$arg1 = $env:CASSANDRA_PARAMS
$arg2 = $env:JVM_OPTS
$arg3 = "-cp $env:CLASSPATH"
$arg4 = @"
"$env:CASSANDRA_MAIN"
"@
$proc = $null
if ($s)
{
echo "Running cassandra with: [$cmd $arg1 $arg2 $arg3 $arg4]"
}
if ($foreground)
{
$cygwin = $false
try
{
$uname = uname -o
if ($uname.CompareTo("Cygwin") -eq 0)
{
$cygwin = $true
}
}
catch
{
# Failed at uname call, not in cygwin
}
if ($cygwin)
{
# if running on cygwin, we cannot capture ctrl+c signals as mintty traps them and then
# SIGKILLs processes, so we'll need to record our $pidfile file for future
# stop-server usage
if (!$p)
{
echo "Detected cygwin runtime environment. Adding -Dcassandra-pidfile=$pidfile to JVM params as control+c trapping on mintty is inconsistent"
$arg2 = $arg2 + " -Dcassandra-pidfile=$pidfile"
}
}
$arg2 = $arg2 + " -Dcassandra-foreground=yes"
$pinfo = New-Object System.Diagnostics.ProcessStartInfo
$pinfo.FileName = "$env:JAVA_BIN"
$pinfo.RedirectStandardInput = $true
$pinfo.UseShellExecute = $false
$pinfo.Arguments = $arg1,$arg2,$arg3,$arg4
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $pinfo
$p.Start() | Out-Null
echo $p.Id > $pidfile
$p.WaitForExit()
}
else
{
$proc = Start-Process -FilePath "$cmd" -ArgumentList $arg1,$arg2,$arg3,$arg4 -PassThru -WindowStyle Hidden
$exitCode = $?
try
{
echo $proc.Id > $pidfile
}
catch
{
echo @"
WARNING! Failed to write pidfile to $pidfile. stop-server.bat and
startup protection will not be available.
"@
echo $_.Exception.Message
exit 1
}
if (-Not $exitCode)
{
exit 1
}
}
}
#-----------------------------------------------------------------------------
Function VerifyPortsAreAvailable
{
# Need to confirm 5 different ports are available or die if any are currently bound
# From cassandra.yaml:
# storage_port
# ssl_storage_port
# native_transport_port
# rpc_port, which we'll match to rpc_address
# and from env: JMX_PORT which we cache in our environment during SetCassandraEnvironment for this check
$toMatch = @("storage_port:","ssl_storage_port:","native_transport_port:","rpc_port")
$yaml = Get-Content "$env:CASSANDRA_CONF\cassandra.yaml"
$listenAddress = ""
$rpcAddress = ""
foreach ($line in $yaml)
{
if ($line -match "^listen_address:")
{
$args = $line -Split ": "
$listenAddress = $args[1] -replace " ", ""
}
if ($line -match "^rpc_address:")
{
$args = $line -Split ": "
$rpcAddress = $args[1] -replace " ", ""
}
}
if ([string]::IsNullOrEmpty($listenAddress))
{
Write-Error "Failed to parse listen_address from cassandra.yaml to check open ports. Aborting startup."
Exit
}
if ([string]::IsNullOrEmpty($rpcAddress))
{
Write-Error "Failed to parse rpc_address from cassandra.yaml to check open ports. Aborting startup."
Exit
}
foreach ($line in $yaml)
{
foreach ($match in $toMatch)
{
if ($line -match "^$match")
{
if ($line.contains("rpc"))
{
CheckPort $rpcAddress $line
}
else
{
CheckPort $listenAddress $line
}
}
}
}
if ([string]::IsNullOrEmpty($env:JMX_PORT))
{
Write-Error "No JMX_PORT is set in environment. Aborting startup."
Exit
}
CheckPort $listenAddress "jmx_port: $env:JMX_PORT"
}
#-----------------------------------------------------------------------------
Function CheckPort([string]$listenAddress, [string]$configLine)
{
$split = $configLine -Split ":"
if ($split.Length -ne 2)
{
echo "Invalid cassandra.yaml config line parsed while checking for available ports:"
echo "$configLine"
echo "Aborting startup"
Exit
}
else
{
$port = $split[1] -replace " ", ""
# start an async connect to the ip/port combo, give it 25ms, and error out if it succeeded
$tcpobject = new-Object system.Net.Sockets.TcpClient
$connect = $tcpobject.BeginConnect($listenAddress, $port, $null, $null)
$wait = $connect.AsyncWaitHandle.WaitOne(25, $false)
if (!$wait)
{
# still trying to connect, if it's not serviced in 25ms we'll assume it's not open
$tcpobject.Close()
}
else
{
$tcpobject.EndConnect($connect) | out-Null
echo "Cassandra port already in use ($configLine). Aborting"
Exit
}
}
}
#-----------------------------------------------------------------------------
Function ValidateArguments
{
if ($install -and $uninstall)
{
echo "Cannot install and uninstall"
exit
}
if ($help)
{
PrintUsage
}
}
Function CheckEmptyParam($param)
{
if ([String]::IsNullOrEmpty($param))
{
echo "Invalid parameter: empty value"
PrintUsage
}
}
for ($i = 0; $i -lt $args.count; $i++)
{
# Skip JVM args
if ($args[$i].StartsWith("-D") -Or $args[$i].StartsWith("-X"))
{
continue;
}
Switch($args[$i])
{
"-install" { $install = $True }
"-uninstall" { $uninstall = $True }
"-help" { PrintUsage }
"-v" { $v = $True }
"-f" { $f = $True }
"-s" { $s = $True }
"-p" { $p = $args[++$i]; CheckEmptyParam($p) }
"-H" { $H = $args[++$i]; CheckEmptyParam($H) }
"-E" { $E = $args[++$i]; CheckEmptyParam($E) }
default
{
"Invalid argument: " + $args[$i];
if (-Not $args[$i].startsWith("-"))
{
echo "Note: All options require -"
}
exit
}
}
}
$pidfile = "pid.txt"
Main