Skip to content

Commit 4060593

Browse files
Merge pull request #64 from Particular/robust_linux_setup
Solidify Linux single- and multinode licenses and cluster operations
2 parents 9d26326 + f170fb6 commit 4060593

File tree

6 files changed

+56
-152
lines changed

6 files changed

+56
-152
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ To test the setup action set the required environment variables and execute `set
9292
$Env:RUNNER_OS=Windows
9393
$Env:RESOURCE_GROUP_OVERRIDE=yourResourceGroup
9494
$Env:REGION_OVERRIDE=yourResourceGroup
95-
.\setup.ps1 -ScriptDirectory . -ContainerName psw-ravendb-1 -SingleConnectionStringName RavenDBConnectionString -ClusterConnectionStringName RavenDBConnectionString -RavenDBLicense 'SingleLineJSON' -RavenDBVersion "5.3" -RavenDBMode "Single" -Tag setup-ravendb-action
95+
.\setup.ps1 -ScriptDirectory . -ContainerName psw-ravendb-1 -SingleConnectionStringName RavenDBConnectionString -ClusterConnectionStringName RavenDBConnectionString -RavenDBLicense 'SingleLineJSON' -RavenDBVersion "6.2" -RavenDBMode "Single" -Tag setup-ravendb-action
9696
```
9797

9898
To test the cleanup action set the required environment variables and execute `cleanup.ps1` with the desired parameters.

clusternodes-compose.yml

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
version: "3"
21
services:
32
leader:
43
container_name: ${CONTAINER_NAME}-leader
@@ -61,31 +60,9 @@ services:
6160
networks:
6261
cluster_network:
6362
ipv4_address: 172.29.1.3
64-
clustersetup:
65-
container_name: clustersetup
66-
image: mcr.microsoft.com/powershell:latest
67-
extra_hosts:
68-
- "host.docker.internal:host-gateway"
69-
volumes:
70-
- ./setup_cluster.ps1:/var/ravendb/setup_cluster.ps1
71-
entrypoint:
72-
[
73-
"pwsh",
74-
"-command",
75-
"&./var/ravendb/setup_cluster.ps1",
76-
"'${LICENSE}'",
77-
"host.docker.internal",
78-
]
79-
restart: "no"
80-
depends_on:
81-
- leader
82-
- follower1
83-
- follower2
84-
networks:
85-
- cluster_network
8663
networks:
8764
cluster_network:
8865
ipam:
8966
driver: default
9067
config:
91-
- subnet: 172.29.0.0/16
68+
- subnet: 172.29.0.0/16

setup.ps1

Lines changed: 53 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -40,21 +40,13 @@ if ($runnerOs -eq "Linux") {
4040

4141
if (($RavenDBMode -eq "Single") -or ($RavenDBMode -eq "Both")) {
4242
docker compose -f singlenode-compose.yml up --detach
43-
$ravenIpsAndPortsToVerify.Add("Single", @{ Ip = "127.0.0.1"; Port = 8080 })
43+
$ravenIpsAndPortsToVerify.Add("Single", @{ Address = "host.docker.internal"; Port = 8080 })
4444
}
4545
if (($RavenDBMode -eq "Cluster") -or ($RavenDBMode -eq "Both")) {
4646
docker compose -f clusternodes-compose.yml up --detach
47-
$ravenIpsAndPortsToVerify.Add("Leader", @{ Ip = "127.0.0.1"; Port = 8081 })
48-
$ravenIpsAndPortsToVerify.Add("Follower1", @{ Ip = "127.0.0.1"; Port = 8082 })
49-
$ravenIpsAndPortsToVerify.Add("Follower2", @{ Ip = "127.0.0.1"; Port = 8083 })
50-
}
51-
52-
# write the connection string to the specified environment variable depending on the mode
53-
if (($RavenDBMode -eq "Single") -or ($RavenDBMode -eq "Both")) {
54-
"$($SingleConnectionStringName)=http://localhost:8080" >> $Env:GITHUB_ENV
55-
}
56-
if (($RavenDBMode -eq "Cluster") -or ($RavenDBMode -eq "Both")) {
57-
"$($ClusterConnectionStringName)=http://localhost:8081,http://localhost:8082,http://localhost:8083" >> $Env:GITHUB_ENV
47+
$ravenIpsAndPortsToVerify.Add("Leader", @{ Address = "host.docker.internal"; Port = 8081 })
48+
$ravenIpsAndPortsToVerify.Add("Follower1", @{ Address = "host.docker.internal"; Port = 8082 })
49+
$ravenIpsAndPortsToVerify.Add("Follower2", @{ Address = "host.docker.internal"; Port = 8083 })
5850
}
5951
}
6052
elseif ($runnerOs -eq "Windows") {
@@ -142,51 +134,60 @@ elseif ($runnerOs -eq "Windows") {
142134
$registryPass = $using:RegistryPass
143135
$detail = NewRavenDBNode $resourceGroup $region $prefix $instanceId $runnerOs $ravenDBVersion $Env:GITHUB_SHA $tag $registryLoginServer $registryUser $registryPass
144136
$hashTable = $using:ravenIpsAndPortsToVerify
145-
$hashTable[$_].Ip = $detail
146-
}
147-
148-
# write the connection string to the specified environment variable depending on the mode
149-
if (($RavenDBMode -eq "Single") -or ($RavenDBMode -eq "Both")) {
150-
"$($SingleConnectionStringName)=http://$($ravenIpsAndPortsToVerify['Single'].Ip):$($ravenIpsAndPortsToVerify['Single'].Port)" >> $Env:GITHUB_ENV
151-
}
152-
if (($RavenDBMode -eq "Cluster") -or ($RavenDBMode -eq "Both")) {
153-
"$($ClusterConnectionStringName)=http://$($ravenIpsAndPortsToVerify['Leader'].Ip):$($ravenIpsAndPortsToVerify['Leader'].Port),http://$($ravenIpsAndPortsToVerify['Follower1'].Ip):$($ravenIpsAndPortsToVerify['Follower1'].Port),http://$($ravenIpsAndPortsToVerify['Follower2'].Ip):$($ravenIpsAndPortsToVerify['Follower2'].Port)" >> $Env:GITHUB_ENV
137+
$hashTable[$_].Address = $detail
154138
}
155139
}
156140
else {
157141
Write-Output "$runnerOs not supported"
158142
exit 1
159143
}
160144

161-
Write-Output "::group::Testing connection"
145+
# write the connection string to the specified environment variable depending on the mode
146+
if (($RavenDBMode -eq "Single") -or ($RavenDBMode -eq "Both")) {
147+
$singleConnectionString = "http://$($ravenIpsAndPortsToVerify['Single'].Address):$($ravenIpsAndPortsToVerify['Single'].Port)"
148+
"$($SingleConnectionStringName)=$($singleConnectionString)" >> $Env:GITHUB_ENV
149+
}
150+
if (($RavenDBMode -eq "Cluster") -or ($RavenDBMode -eq "Both")) {
151+
$clusterConnectionString = "http://$($ravenIpsAndPortsToVerify['Leader'].Address):$($ravenIpsAndPortsToVerify['Leader'].Port),http://$($ravenIpsAndPortsToVerify['Follower1'].Address):$($ravenIpsAndPortsToVerify['Follower1'].Port),http://$($ravenIpsAndPortsToVerify['Follower2'].Address):$($ravenIpsAndPortsToVerify['Follower2'].Port)"
152+
"$($ClusterConnectionStringName)=$($clusterConnectionString)" >> $Env:GITHUB_ENV
153+
}
154+
155+
Write-Output "::group::Testing HTTP connectivity"
162156

163157
$connectionErrors = [hashtable]::Synchronized(@{})
164158
@($ravenIpsAndPortsToVerify.keys) | ForEach-Object -Parallel {
165159
$startDate = Get-Date
166160
$errorTable = $using:connectionErrors
167161
$hashTable = $using:ravenIpsAndPortsToVerify
168-
$tcpClient = New-Object Net.Sockets.TcpClient
169162
$nodeName = $_
170163
$nodeInfo = $hashTable[$nodeName]
171-
Write-Output "::add-mask::$($nodeInfo.Ip)"
172-
Write-Output "Verifying connection $nodeName"
164+
$nodeUrl = "http://$($nodeInfo.Address):$($nodeInfo.Port)"
165+
Write-Output "::add-mask::$($nodeInfo.Address)"
166+
Write-Output "Verifying HTTP connection to $nodeName at $nodeUrl"
167+
168+
$connected = $false
173169
do {
174170
try {
175-
Write-Output "Trying to connect to $nodeName on port $($nodeInfo.Port)"
176-
$tcpClient.Connect($nodeInfo.Ip, $nodeInfo.Port)
177-
Write-Output "Connection to $nodeName successful"
171+
Write-Output "Trying HTTP connection to $nodeName at $nodeUrl"
172+
173+
$response = Invoke-WebRequest "$nodeUrl/admin/stats" -Method GET -UseBasicParsing -TimeoutSec 30
174+
if ($response.StatusCode -ge 200 -and $response.StatusCode -lt 300) {
175+
$connected = $true
176+
Write-Output "HTTP connection to $nodeName successful - Status: $($response.StatusCode)"
177+
}
178178
}
179179
catch {
180180
if ($startDate.AddMinutes(5) -lt (Get-Date)) {
181-
$errorTable[$nodeName] = "Unable to connect to $nodeName"
181+
$errorTable[$nodeName] = "Unable to establish HTTP connection to $nodeName at $nodeUrl"
182182
break
183183
}
184+
Write-Output "HTTP connection attempt failed, retrying in 10 seconds..."
184185
Start-Sleep -Seconds 10
185186
}
186-
} While ($tcpClient.Connected -ne "True")
187-
$tcpClient.Close()
187+
} While (-not $connected)
188+
188189
if (-not $errorTable.ContainsKey($nodeName)) {
189-
Write-Output "Connection to $nodeName verified"
190+
Write-Output "HTTP connection to $nodeName verified"
190191
}
191192
}
192193

@@ -198,9 +199,7 @@ if ($connectionErrors.Count -gt 0) {
198199

199200
Write-Output "::endgroup::"
200201

201-
# This is not entirely nice because the activitation for linux happens inside the compose infrastructure while for windows
202-
# we have to do it here. The cluster checks during the setup phase whether it can reach the nodes and that was easier to do within
203-
# the compose setup container. Maybe one day we will find a way to clean this up a bit.
202+
Write-Output "::group::Licensing and setting up node(s)"
204203

205204
function ValidateRavenLicense {
206205
param (
@@ -226,48 +225,55 @@ function ValidateRavenLicense {
226225
}
227226
}
228227

229-
if ($runnerOs -eq "Windows" -and (($RavenDBMode -eq "Single") -or ($RavenDBMode -eq "Both"))) {
228+
if (($RavenDBMode -eq "Single") -or ($RavenDBMode -eq "Both")) {
230229
Write-Output "Activating License on Single Node"
231230

232-
Invoke-WebRequest "http://$($ravenIpsAndPortsToVerify['Single'].Ip):$($ravenIpsAndPortsToVerify['Single'].Port)/admin/license/activate" -Method POST -Headers @{ 'Content-Type' = 'application/json'; 'charset' = 'UTF-8' } -Body "$($FormattedRavenDBLicense)" -MaximumRetryCount 5 -RetryIntervalSec 10 -ConnectionTimeoutSeconds 30
231+
$singleNodeUrl = $singleConnectionString
232+
233+
Invoke-WebRequest "$($singleNodeUrl)/admin/license/activate" -Method POST -Headers @{ 'Content-Type' = 'application/json'; 'charset' = 'UTF-8' } -Body "$($FormattedRavenDBLicense)" -MaximumRetryCount 5 -RetryIntervalSec 10 -ConnectionTimeoutSeconds 30
233234
if (!$?) {
234235
Write-Error "Unable to activate RavenDB license on single-node server"
235236
exit -1
236237
}
237238

238-
ValidateRavenLicense "Single-Node Server" "$($ravenIpsAndPortsToVerify['Single'].Ip):$($ravenIpsAndPortsToVerify['Single'].Port)"
239+
ValidateRavenLicense "Single-Node Server" ([Uri]$singleNodeUrl).Authority
239240
}
240-
if ($runnerOs -eq "Windows" -and (($RavenDBMode -eq "Cluster") -or ($RavenDBMode -eq "Both"))) {
241+
if (($RavenDBMode -eq "Cluster") -or ($RavenDBMode -eq "Both")) {
241242
Write-Output "Activating License on leader in the cluster"
242243

243-
$leader = "$($ravenIpsAndPortsToVerify['Leader'].Ip):$($ravenIpsAndPortsToVerify['Leader'].Port)"
244+
$clusterUrls = $clusterConnectionString.Split(",")
245+
246+
# First URL is always the leader
247+
$leaderUrl = $clusterUrls[0]
248+
244249
# Once you set the license on a node, it assumes the node to be a cluster, so only set the license on the leader
245-
Invoke-WebRequest "http://$($leader)/admin/license/activate" -Method POST -Headers @{ 'Content-Type' = 'application/json'; 'charset' = 'UTF-8' } -Body "$($FormattedRavenDBLicense)" -MaximumRetryCount 5 -RetryIntervalSec 10 -ConnectionTimeoutSeconds 30
250+
Invoke-WebRequest "$($leaderUrl)/admin/license/activate" -Method POST -Headers @{ 'Content-Type' = 'application/json'; 'charset' = 'UTF-8' } -Body "$($FormattedRavenDBLicense)" -MaximumRetryCount 5 -RetryIntervalSec 10 -ConnectionTimeoutSeconds 30
246251
if (!$?) {
247252
Write-Error "Unable to activate RavenDB license on cluster leader"
248253
exit -1
249254
}
250255

251-
ValidateRavenLicense "Cluster Leader" $leader
256+
ValidateRavenLicense "Cluster Leader" ([Uri]$leaderUrl).Authority
252257

253258
Write-Output "Establish the cluster relationship"
254-
Invoke-WebRequest "http://$($leader)/admin/license/set-limit?nodeTag=A&newAssignedCores=1" -Method POST -Headers @{ 'Content-Type' = 'application/json'; 'Context-Length' = '0'; 'charset' = 'UTF-8' } -MaximumRetryCount 5 -RetryIntervalSec 10 -ConnectionTimeoutSeconds 30
259+
Invoke-WebRequest "$($leaderUrl)/admin/license/set-limit?nodeTag=A&newAssignedCores=1" -Method POST -Headers @{ 'Content-Type' = 'application/json'; 'Context-Length' = '0'; 'charset' = 'UTF-8' } -MaximumRetryCount 5 -RetryIntervalSec 10 -ConnectionTimeoutSeconds 30
255260
if (!$?) {
256261
Write-Error "Unable to set license limitations on cluster leader"
257262
exit -1
258263
}
259264

260-
$encodedURL = [System.Web.HttpUtility]::UrlEncode("http://$($ravenIpsAndPortsToVerify['Follower1'].Ip):$($ravenIpsAndPortsToVerify['Follower1'].Port)")
261-
Invoke-WebRequest "http://$($leader)/admin/cluster/node?url=$($encodedURL)&tag=B&watcher=true&assignedCores=1" -Method PUT -Headers @{ 'Content-Type' = 'application/json'; 'Context-Length' = '0'; 'charset' = 'UTF-8' } -MaximumRetryCount 5 -RetryIntervalSec 10 -ConnectionTimeoutSeconds 30
265+
$encodedURL = [System.Web.HttpUtility]::UrlEncode("http://$($ravenIpsAndPortsToVerify['Follower1'].Address):$($ravenIpsAndPortsToVerify['Follower1'].Port)")
266+
Invoke-WebRequest "$($leaderUrl)/admin/cluster/node?url=$($encodedURL)&tag=B&watcher=true&assignedCores=1" -Method PUT -Headers @{ 'Content-Type' = 'application/json'; 'Context-Length' = '0'; 'charset' = 'UTF-8' } -MaximumRetryCount 5 -RetryIntervalSec 10 -ConnectionTimeoutSeconds 30
262267
if (!$?) {
263268
Write-Error "Unable to join Follower1 to cluster"
264269
exit -1
265270
}
266271

267-
$encodedURL = [System.Web.HttpUtility]::UrlEncode("http://$($ravenIpsAndPortsToVerify['Follower2'].Ip):$($ravenIpsAndPortsToVerify['Follower2'].Port)")
268-
Invoke-WebRequest "http://$($leader)/admin/cluster/node?url=$($encodedURL)&tag=C&watcher=true&assignedCores=1" -Method PUT -Headers @{ 'Content-Type' = 'application/json'; 'Context-Length' = '0'; 'charset' = 'UTF-8' } -MaximumRetryCount 5 -RetryIntervalSec 10 -ConnectionTimeoutSeconds 30
272+
$encodedURL = [System.Web.HttpUtility]::UrlEncode("http://$($ravenIpsAndPortsToVerify['Follower2'].Address):$($ravenIpsAndPortsToVerify['Follower2'].Port)")
273+
Invoke-WebRequest "$($leaderUrl)/admin/cluster/node?url=$($encodedURL)&tag=C&watcher=true&assignedCores=1" -Method PUT -Headers @{ 'Content-Type' = 'application/json'; 'Context-Length' = '0'; 'charset' = 'UTF-8' } -MaximumRetryCount 5 -RetryIntervalSec 10 -ConnectionTimeoutSeconds 30
269274
if (!$?) {
270275
Write-Error "Unable to join Follower 2 to cluster"
271276
exit -1
272277
}
273278
}
279+
Write-Output "::endgroup::"

setup_cluster.ps1

Lines changed: 0 additions & 33 deletions
This file was deleted.

setup_singlenode.ps1

Lines changed: 0 additions & 25 deletions
This file was deleted.

singlenode-compose.yml

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
version: "3"
21
services:
32
singlenode:
43
container_name: ${CONTAINER_NAME}
@@ -19,29 +18,9 @@ services:
1918
networks:
2019
singlenode_network:
2120
ipv4_address: 172.28.1.1
22-
singlenodesetup:
23-
container_name: singlenodesetup
24-
image: mcr.microsoft.com/powershell:latest
25-
extra_hosts:
26-
- "host.docker.internal:host-gateway"
27-
volumes:
28-
- ./setup_singlenode.ps1:/var/ravendb/setup_singlenode.ps1
29-
entrypoint:
30-
[
31-
"pwsh",
32-
"-command",
33-
"&./var/ravendb/setup_singlenode.ps1",
34-
"'${LICENSE}'",
35-
"host.docker.internal",
36-
]
37-
restart: "no"
38-
depends_on:
39-
- singlenode
40-
networks:
41-
- singlenode_network
4221
networks:
4322
singlenode_network:
4423
ipam:
4524
driver: default
4625
config:
47-
- subnet: 172.28.0.0/16
26+
- subnet: 172.28.0.0/16

0 commit comments

Comments
 (0)