Skip to content

Commit 012ccad

Browse files
authored
Add IoTDB Edge distribution: ConfigNode + DataNode merged in one JVM (#18538)
1 parent 07d6a21 commit 012ccad

26 files changed

Lines changed: 2008 additions & 0 deletions

File tree

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
#
2+
# Licensed to the Apache Software Foundation (ASF) under one
3+
# or more contributor license agreements. See the NOTICE file
4+
# distributed with this work for additional information
5+
# regarding copyright ownership. The ASF licenses this file
6+
# to you under the Apache License, Version 2.0 (the
7+
# "License"); you may not use this file except in compliance
8+
# with the License. You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing,
13+
# software distributed under the License is distributed on an
14+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
# KIND, either express or implied. See the License for the
16+
# specific language governing permissions and limitations
17+
# under the License.
18+
#
19+
20+
$ErrorActionPreference = 'Stop'
21+
$repositoryRoot = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
22+
$testRoot = Join-Path ([System.IO.Path]::GetTempPath()) ('iotdb-edge-windows-' + [guid]::NewGuid())
23+
New-Item -ItemType Directory -Path $testRoot | Out-Null
24+
$javaStub = Join-Path $testRoot 'java.exe'
25+
$script:caseCount = 0
26+
$portNames = @(
27+
'cn_internal_port', 'cn_consensus_port', 'dn_rpc_port', 'dn_internal_port',
28+
'dn_mpp_data_exchange_port', 'dn_schema_region_consensus_port', 'dn_data_region_consensus_port'
29+
)
30+
31+
function Get-FreePorts {
32+
$result = @{}
33+
$reservations = @()
34+
try {
35+
foreach ($name in $portNames) {
36+
$listener = [System.Net.Sockets.TcpListener]::new([System.Net.IPAddress]::Loopback, 0)
37+
$listener.Start()
38+
$reservations += $listener
39+
$result[$name] = $listener.LocalEndpoint.Port
40+
}
41+
return $result
42+
} finally {
43+
foreach ($listener in $reservations) {
44+
$listener.Stop()
45+
}
46+
}
47+
}
48+
49+
function Invoke-EdgeLauncher {
50+
param(
51+
[System.Collections.IDictionary]$Ports,
52+
[string]$Locale = '',
53+
[string[]]$ExtraLines = @(),
54+
[switch]$MissingConfig
55+
)
56+
57+
$script:caseCount++
58+
$caseDir = Join-Path $testRoot "case-$script:caseCount"
59+
$edgeHome = Join-Path $caseDir 'edge installation'
60+
$configDir = Join-Path $caseDir 'custom configuration'
61+
$launcherDir = Join-Path $edgeHome 'sbin/windows'
62+
$envDir = Join-Path $configDir 'windows'
63+
$javaHome = Join-Path $caseDir 'fake java'
64+
$javaBin = Join-Path $javaHome 'bin'
65+
New-Item -ItemType Directory -Path $launcherDir, $envDir, $javaBin -Force | Out-Null
66+
foreach ($file in @('start-edge.bat', 'check-edge.ps1')) {
67+
Copy-Item -LiteralPath (Join-Path $repositoryRoot "scripts/sbin/windows/$file") -Destination $launcherDir
68+
}
69+
Copy-Item -LiteralPath (Join-Path $repositoryRoot 'scripts/conf/windows/edge-env.bat') -Destination $envDir
70+
$common = Get-Content -LiteralPath (Join-Path $repositoryRoot 'scripts/conf/windows/iotdb-common.bat') -Raw
71+
Set-Content -LiteralPath (Join-Path $envDir 'iotdb-common.bat') -Value $common.Replace('@tsfile.locale.opt@', $Locale) -Encoding ASCII
72+
if (-not $MissingConfig) {
73+
$lines = @($ExtraLines)
74+
foreach ($key in $Ports.Keys) {
75+
$lines += " $key = $($Ports[$key]) "
76+
}
77+
Set-Content -LiteralPath (Join-Path $configDir 'iotdb-system.properties') -Value $lines -Encoding ASCII
78+
}
79+
80+
Copy-Item -LiteralPath $javaStub -Destination $javaBin
81+
$argsFile = Join-Path $caseDir 'java-arguments.txt'
82+
$startInfo = [System.Diagnostics.ProcessStartInfo]::new()
83+
$startInfo.FileName = $env:ComSpec
84+
$startInfo.Arguments = '/d /c call "' + (Join-Path $launcherDir 'start-edge.bat') + '"'
85+
$startInfo.WorkingDirectory = $caseDir
86+
$startInfo.UseShellExecute = $false
87+
$startInfo.CreateNoWindow = $true
88+
$startInfo.RedirectStandardOutput = $true
89+
$startInfo.RedirectStandardError = $true
90+
$startInfo.RedirectStandardInput = $true
91+
$startInfo.EnvironmentVariables['IOTDB_HOME'] = $edgeHome
92+
$startInfo.EnvironmentVariables['IOTDB_CONF'] = $configDir
93+
$startInfo.EnvironmentVariables['JAVA_HOME'] = $javaHome
94+
$startInfo.EnvironmentVariables['EDGE_TEST_JAVA_ARGS'] = $argsFile
95+
$startInfo.EnvironmentVariables['IOTDB_JMX_OPTS'] = ''
96+
$startInfo.EnvironmentVariables['TSFILE_LOCALE_JVM_OPT'] = '-Dtsfile.locale=stale'
97+
$process = [System.Diagnostics.Process]::new()
98+
$process.StartInfo = $startInfo
99+
try {
100+
[void]$process.Start()
101+
$stdout = $process.StandardOutput.ReadToEndAsync()
102+
$stderr = $process.StandardError.ReadToEndAsync()
103+
$process.StandardInput.Close()
104+
if (-not $process.WaitForExit(30000)) {
105+
$process.Kill()
106+
throw 'Timed out running the Edge Windows launcher'
107+
}
108+
return [pscustomobject]@{
109+
ExitCode = $process.ExitCode
110+
Output = $stdout.Result + $stderr.Result
111+
JavaInvoked = Test-Path -LiteralPath $argsFile
112+
Arguments = if (Test-Path -LiteralPath $argsFile) { Get-Content -LiteralPath $argsFile -Raw } else { '' }
113+
}
114+
} finally {
115+
$process.Dispose()
116+
}
117+
}
118+
119+
function Assert-LaunchResult {
120+
param($Result, [bool]$ShouldLaunch, [string]$Name)
121+
if ($ShouldLaunch) {
122+
if ($Result.ExitCode -ne 0 -or -not $Result.JavaInvoked) {
123+
throw "${Name}: expected a successful Java launch. $($Result.Output)"
124+
}
125+
if ($Result.Arguments -notmatch 'org\.apache\.iotdb\.edge\.EdgeNode') {
126+
throw "${Name}: the Edge main class was not invoked"
127+
}
128+
} elseif ($Result.ExitCode -eq 0 -or $Result.JavaInvoked) {
129+
throw "${Name}: expected rejection before starting Java. $($Result.Output)"
130+
}
131+
Write-Host "PASS: $Name"
132+
}
133+
134+
try {
135+
# Use an executable stub so batch control flow and argument quoting match a real JVM.
136+
Add-Type -OutputAssembly $javaStub -OutputType ConsoleApplication -TypeDefinition @'
137+
using System;
138+
using System.IO;
139+
140+
internal static class EdgeJavaStub
141+
{
142+
private static int Main(string[] args)
143+
{
144+
if (args.Length == 1 && args[0] == "-fullversion")
145+
{
146+
Console.Error.WriteLine("openjdk full version \"17.0.5+8\"");
147+
return 0;
148+
}
149+
File.WriteAllLines(Environment.GetEnvironmentVariable("EDGE_TEST_JAVA_ARGS"), args);
150+
return 0;
151+
}
152+
}
153+
'@
154+
155+
$freePorts = Get-FreePorts
156+
foreach ($locale in @('', '-Dtsfile.locale=zh')) {
157+
$result = Invoke-EdgeLauncher -Ports $freePorts -Locale $locale
158+
Assert-LaunchResult $result $true "locale '$locale', custom config and paths with spaces"
159+
if ($locale -eq '') {
160+
if ($result.Arguments -match '-Dtsfile\.locale=') {
161+
throw 'The default package inherited a stale TsFile locale option'
162+
}
163+
} elseif ([regex]::Matches($result.Arguments, '-Dtsfile\.locale=zh').Count -ne 1 -or $result.Arguments -match 'tsfile\.locale=stale') {
164+
throw 'The zh package did not apply exactly one filtered TsFile locale option'
165+
}
166+
}
167+
168+
foreach ($name in $portNames) {
169+
$listener = [System.Net.Sockets.TcpListener]::new([System.Net.IPAddress]::Loopback, 0)
170+
$listener.Start()
171+
try {
172+
$ports = $freePorts.Clone()
173+
$ports[$name] = $listener.LocalEndpoint.Port
174+
$result = Invoke-EdgeLauncher -Ports $ports
175+
Assert-LaunchResult $result $false "occupied $name"
176+
if ($result.Output -notmatch "The $name $($ports[$name]) is already occupied") {
177+
throw "The occupied port was not identified correctly: $($result.Output)"
178+
}
179+
} finally {
180+
$listener.Stop()
181+
}
182+
}
183+
184+
$listener = [System.Net.Sockets.TcpListener]::new([System.Net.IPAddress]::Loopback, 0)
185+
$listener.Start()
186+
try {
187+
$result = Invoke-EdgeLauncher -Ports $freePorts -ExtraLines @(
188+
"# cn_internal_port=$($listener.LocalEndpoint.Port)",
189+
"! dn_rpc_port=$($listener.LocalEndpoint.Port)",
190+
"cn_internal_port=$($listener.LocalEndpoint.Port)"
191+
)
192+
Assert-LaunchResult $result $true 'comments, whitespace and the last value of a repeated property'
193+
} finally {
194+
$listener.Stop()
195+
}
196+
197+
$listener = [System.Net.Sockets.TcpListener]::new([System.Net.IPAddress]::Loopback, 10720)
198+
$listener.Start()
199+
try {
200+
$ports = $freePorts.Clone()
201+
$ports.Remove('cn_consensus_port')
202+
$result = Invoke-EdgeLauncher -Ports $ports
203+
Assert-LaunchResult $result $false 'default port for an omitted property'
204+
$result = Invoke-EdgeLauncher -Ports @{} -MissingConfig
205+
Assert-LaunchResult $result $false 'default ports when the configuration file is absent'
206+
if ($result.Output -notmatch 'cn_consensus_port 10720 is already occupied') {
207+
throw 'The missing-file fallback did not check the default ConfigNode consensus port'
208+
}
209+
} finally {
210+
$listener.Stop()
211+
}
212+
213+
foreach ($value in @('0', '65536', 'not-a-port')) {
214+
$ports = $freePorts.Clone()
215+
$ports['dn_rpc_port'] = $value
216+
$result = Invoke-EdgeLauncher -Ports $ports
217+
Assert-LaunchResult $result $false "invalid port '$value'"
218+
}
219+
Write-Host "All $script:caseCount Windows Edge launcher cases passed."
220+
} finally {
221+
Remove-Item -LiteralPath $testRoot -Recurse -Force
222+
}

‎.github/workflows/edge-it.yml‎

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
#
2+
# Licensed to the Apache Software Foundation (ASF) under one
3+
# or more contributor license agreements. See the NOTICE file
4+
# distributed with this work for additional information
5+
# regarding copyright ownership. The ASF licenses this file
6+
# to you under the Apache License, Version 2.0 (the
7+
# "License"); you may not use this file except in compliance
8+
# with the License. You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing,
13+
# software distributed under the License is distributed on an
14+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
# KIND, either express or implied. See the License for the
16+
# specific language governing permissions and limitations
17+
# under the License.
18+
#
19+
20+
name: Edge IT
21+
22+
on:
23+
push:
24+
branches:
25+
- master
26+
- "rel/*"
27+
- "rc/*"
28+
paths-ignore:
29+
- "docs/**"
30+
- "site/**"
31+
- "iotdb-client/client-cpp/**"
32+
- ".github/workflows/client-cpp-package.yml"
33+
- ".github/scripts/package-client-cpp-*.sh"
34+
- ".github/workflows/multi-language-client.yml"
35+
pull_request:
36+
branches:
37+
- master
38+
- "rel/*"
39+
- "rc/*"
40+
paths-ignore:
41+
- "docs/**"
42+
- "site/**"
43+
- "iotdb-client/client-cpp/**"
44+
- ".github/workflows/client-cpp-package.yml"
45+
- ".github/scripts/package-client-cpp-*.sh"
46+
- ".github/workflows/multi-language-client.yml"
47+
workflow_dispatch:
48+
49+
concurrency:
50+
group: ${{ github.workflow }}-${{ github.ref }}
51+
cancel-in-progress: true
52+
53+
env:
54+
MAVEN_OPTS: -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -Dmaven.wagon.http.retryHandler.class=standard -Dmaven.wagon.http.retryHandler.count=3
55+
MAVEN_ARGS: --batch-mode --no-transfer-progress
56+
DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
57+
58+
jobs:
59+
Ubuntu:
60+
runs-on: ubuntu-latest
61+
62+
steps:
63+
- uses: actions/checkout@v5
64+
- name: Set up JDK
65+
uses: actions/setup-java@v5
66+
with:
67+
distribution: corretto
68+
java-version: 17
69+
env:
70+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
71+
- name: Cache Maven packages
72+
uses: actions/cache@v5
73+
with:
74+
path: ~/.m2
75+
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
76+
restore-keys: ${{ runner.os }}-m2-
77+
- name: Build Edge distribution
78+
shell: bash
79+
run: |
80+
mvn clean package \
81+
-DskipTests \
82+
-pl distribution \
83+
-am
84+
- name: Edge IT
85+
shell: bash
86+
run: |
87+
mvn verify \
88+
-P with-integration-tests,EdgeIT \
89+
-DskipUTs \
90+
-Dit.test=IoTDBEdgeBasicIT \
91+
-DEdgeConfigNodeAddress=127.0.0.2 \
92+
-DfailIfNoTests=false \
93+
-Dfailsafe.failIfNoSpecifiedTests=false \
94+
-pl integration-test \
95+
-am
96+
- name: Upload Artifact
97+
if: failure()
98+
uses: actions/upload-artifact@v6
99+
with:
100+
name: edge-log-Linux
101+
path: |
102+
integration-test/target/edge-it/**/*.log
103+
integration-test/target/failsafe-reports
104+
if-no-files-found: ignore
105+
retention-days: 1
106+
107+
WindowsScripts:
108+
runs-on: windows-latest
109+
steps:
110+
- uses: actions/checkout@v5
111+
- name: Test Edge Windows launchers
112+
shell: powershell
113+
run: .github/scripts/test-edge-windows.ps1

‎CLAUDE.md‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@ mvn clean verify -DskipUTs -Dit.test=ClassName -DfailIfNoTests=false -Dfailsafe.
9292

9393
# Run a single test method within an IT class (use ClassName#methodName)
9494
mvn clean verify -DskipUTs -Dit.test=ClassName#methodName -DfailIfNoTests=false -Dfailsafe.failIfNoSpecifiedTests=false -pl integration-test -am -PTableSimpleIT -P with-integration-tests
95+
96+
# Build the Edge distribution and run its dedicated Tree/Table read-write IT
97+
mvn clean package -DskipTests -pl distribution -am
98+
mvn verify -DskipUTs -Dit.test=IoTDBEdgeBasicIT -DfailIfNoTests=false -Dfailsafe.failIfNoSpecifiedTests=false -pl integration-test -am -P EdgeIT -P with-integration-tests
9599
```
96100

97101
When verifying a new feature, only run the specific IT classes/methods that were added or modified in the current branch — do not run all ITs.
@@ -113,6 +117,15 @@ To run integration tests from IntelliJ: enable the `with-integration-tests` prof
113117
- **DataNode** (`iotdb-core/datanode`): Handles data storage, query execution, and client connections. The main server component.
114118
- **AINode** (`iotdb-core/ainode`): Python-based node for AI/ML inference tasks.
115119

120+
### Edge Distribution
121+
122+
- `EdgeNode` (`iotdb-core/confignode/src/main/java/org/apache/iotdb/edge/EdgeNode.java`) starts the ConfigNode and DataNode in one JVM for resource-constrained single-node deployments.
123+
- `distribution/src/assembly/edge.xml` produces the additional `apache-iotdb-<version>-edge-bin.zip` artifact. Keep its `edge-bin` assembly id unique so it cannot replace an existing attached artifact.
124+
- Edge-specific system defaults live under `iotdb-core/node-commons/src/assembly/resources/conf/edge/`. Standard node, all-in-one, and integration-test assemblies must continue to exclude `edge/**`.
125+
- Edge launchers must reuse the configuration-aware port checks in `scripts/conf/iotdb-common.sh`. Stop scripts must match both the `EdgeNode` main class and the exact `IOTDB_HOME`; never fall back to a global process-name kill.
126+
- Package node-independent tools that work with the combined process. Exclude scripts that require standalone node launchers or environment files, including destructive, daemon-management, and health-check scripts.
127+
- After packaging changes, run a clean distribution build, check that every existing distribution artifact is still produced, and inspect the Edge zip contents and startup/shutdown behavior.
128+
116129
### Dual Data Model
117130

118131
IoTDB supports two data models operating on the same storage:

‎README.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,18 @@ Under the root path of iotdb:
303303
304304
After being built, the IoTDB distribution is located at the folder: "distribution/target".
305305
306+
### Build IoTDB Edge
307+
308+
The distribution build also produces `apache-iotdb-<version>-edge-bin.zip`. IoTDB Edge runs the ConfigNode and DataNode in one JVM for resource-constrained, single-node deployments. It is an additional artifact and does not replace any existing distribution package.
309+
310+
After extracting the package, configure `conf/iotdb-system.properties`, then start or stop the Edge process with:
311+
312+
```bash
313+
sbin/start-edge.sh
314+
sbin/stop-edge.sh
315+
```
316+
317+
On Windows, use `sbin\windows\start-edge.bat` and `sbin\windows\stop-edge.bat`. The package retains tools that are compatible with the combined Edge process.
306318

307319
### Only build cli
308320

0 commit comments

Comments
 (0)