Skip to content

Commit 0ed6fce

Browse files
author
MPCoreDeveloper
committed
migration docs
1 parent f2ecc50 commit 0ed6fce

10 files changed

Lines changed: 328 additions & 31 deletions

File tree

.github/workflows/ci.yml

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ env:
1515
DOTNET_CLI_TELEMETRY_OPTOUT: true
1616
DOTNET_NOLOGO: true
1717
CI_TEST_FILTER: 'Category!=Performance&Category!=Debug&Category!=Manual'
18+
MIN_LINE_COVERAGE_PERCENT: '60'
1819

1920
jobs:
2021
build:
@@ -97,7 +98,47 @@ jobs:
9798
env:
9899
CI: "true"
99100
GITHUB_ACTIONS: "true"
100-
101+
102+
- name: Validate coverage threshold
103+
if: matrix.os == 'ubuntu-latest'
104+
shell: python3 {0}
105+
run: |
106+
import glob
107+
import os
108+
import sys
109+
import xml.etree.ElementTree as ET
110+
111+
threshold = float(os.environ.get("MIN_LINE_COVERAGE_PERCENT", "60"))
112+
files = glob.glob("**/TestResults/**/coverage.cobertura.xml", recursive=True)
113+
114+
if not files:
115+
print("No coverage.cobertura.xml files found.", file=sys.stderr)
116+
sys.exit(1)
117+
118+
covered = 0
119+
valid = 0
120+
for file in files:
121+
try:
122+
root = ET.parse(file).getroot()
123+
lines_valid = int(root.attrib.get("lines-valid", "0"))
124+
lines_covered = int(root.attrib.get("lines-covered", "0"))
125+
valid += lines_valid
126+
covered += lines_covered
127+
print(f"Coverage input: {file} -> {lines_covered}/{lines_valid}")
128+
except Exception as ex:
129+
print(f"Skipping unreadable coverage file {file}: {ex}")
130+
131+
if valid == 0:
132+
print("Coverage reports found but lines-valid is 0.", file=sys.stderr)
133+
sys.exit(1)
134+
135+
percent = (covered / valid) * 100.0
136+
print(f"Merged line coverage: {percent:.2f}% (threshold: {threshold:.2f}%)")
137+
138+
if percent < threshold:
139+
print("Coverage threshold not met.", file=sys.stderr)
140+
sys.exit(1)
141+
101142
- name: Upload test results
102143
if: always()
103144
uses: actions/upload-artifact@v4

.github/workflows/compatibility-smoke.yml

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ on:
1010
timeout:
1111
description: Seconds to wait for server ready
1212
required: false
13-
default: '90'
13+
default: '180'
1414

1515
permissions:
1616
contents: read
@@ -26,13 +26,13 @@ env:
2626
SMOKE_PASSWORD: admin123
2727
SMOKE_DATABASE: smokedb
2828
SMOKE_CERT_PASS: smoketest
29-
SERVER_READY_TIMEOUT: ${{ github.event.inputs.timeout || '90' }}
29+
SERVER_READY_TIMEOUT: ${{ github.event.inputs.timeout || '180' }}
3030

3131
jobs:
3232
compatibility-smoke:
3333
name: Server Compatibility Smoke
3434
runs-on: ubuntu-latest
35-
timeout-minutes: 15
35+
timeout-minutes: 20
3636

3737
steps:
3838
- name: Checkout
@@ -109,26 +109,42 @@ jobs:
109109
110110
port = int(os.environ["SMOKE_HTTPS_PORT"])
111111
timeout = int(os.environ["SERVER_READY_TIMEOUT"])
112-
url = f"https://127.0.0.1:{port}/api/v1/health"
112+
urls = [
113+
f"https://127.0.0.1:{port}/api/v1/health",
114+
f"https://127.0.0.1:{port}/health"
115+
]
116+
113117
ctx = ssl.create_default_context()
114118
ctx.check_hostname = False
115119
ctx.verify_mode = ssl.CERT_NONE
116120
117-
print(f"Polling {url} for up to {timeout}s …", flush=True)
121+
print(f"Polling {urls[0]} / fallback {urls[1]} for up to {timeout}s …", flush=True)
118122
deadline = time.monotonic() + timeout
123+
last_error = None
119124
while time.monotonic() < deadline:
120-
try:
121-
with urllib.request.urlopen(url, context=ctx, timeout=3) as r:
122-
if r.status == 200:
123-
print("Server is healthy.", flush=True)
124-
sys.exit(0)
125-
except Exception as e:
126-
pass
125+
for url in urls:
126+
try:
127+
with urllib.request.urlopen(url, context=ctx, timeout=3) as r:
128+
if r.status == 200:
129+
print(f"Server is healthy via {url}", flush=True)
130+
sys.exit(0)
131+
except Exception as e:
132+
last_error = e
127133
time.sleep(2)
128134
129135
print(f"Server not ready after {timeout}s — failing.", file=sys.stderr)
136+
if last_error is not None:
137+
print(f"Last readiness error: {last_error}", file=sys.stderr)
130138
sys.exit(1)
131139
140+
- name: Dump server logs on readiness failure
141+
if: failure()
142+
run: |
143+
echo "=== Server stdout/stderr (tail -200) ==="
144+
tail -n 200 tests/CompatibilitySmoke/smoke-logs/server-stdout.log || true
145+
echo "=== Smoke log file (tail -200) ==="
146+
tail -n 200 tests/CompatibilitySmoke/smoke-logs/smoke.log || true
147+
132148
- name: Run compatibility smoke tests
133149
run: |
134150
python3 tests/CompatibilitySmoke/smoke_tests.py \

docs/OPEN_ITEMS_v1.7.0.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,13 @@
2121
- `docs/storage/METADATA_IMPROVEMENTS_V1.5.0.md``docs/storage/METADATA_IMPROVEMENTS_v1.7.0.md`
2222
- `docs/storage/QUICK_REFERENCE_V1.5.0.md``docs/storage/QUICK_REFERENCE_v1.7.0.md`
2323
- Updated `docs/release/PHASE12_RELEASE_NOTES.md` version references to `v1.7.0`.
24+
- Fixed CI compatibility smoke readiness failures:
25+
- Added server `GET /api/v1/health` endpoint to match smoke test expectations.
26+
- Added TLS PFX password support in server startup configuration.
27+
- Increased workflow readiness timeout and added startup log dump on readiness failures.
28+
- Added CI coverage threshold validation in `.github/workflows/ci.yml`:
29+
- Aggregates Cobertura line metrics from generated test coverage files.
30+
- Fails CI when merged line coverage drops below `MIN_LINE_COVERAGE_PERCENT`.
2431

2532
## Open items intentionally left for dedicated follow-up
2633

docs/migration/FLUENTMIGRATOR_EMBEDDED_MODE_v1.7.0.md

Lines changed: 81 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,23 @@
55

66
---
77

8+
## 0. Short Answer for the Most Common Question
9+
10+
**Question:** Does FluentMigrator only work with a remote SharpCoreDB server because the built-in `ISharpCoreDbMigrationSqlExecutor` implementation is the gRPC one?
11+
12+
**Answer:** **No. Embedded mode is fully supported.**
13+
14+
The gRPC executor is only the built-in implementation of the **optional custom executor extension point**. It is used for **remote** execution scenarios.
15+
16+
For **embedded/in-process** execution, FluentMigrator does **not** require any `ISharpCoreDbMigrationSqlExecutor` registration. The migration pipeline runs directly against:
17+
18+
1. `IDatabase` (preferred embedded path)
19+
2. `DbConnection` (fallback path)
20+
21+
If your application registers SharpCoreDB locally and calls `AddSharpCoreDBFluentMigrator(...)`, migrations execute locally in-process without gRPC.
22+
23+
---
24+
825
## 1. What Embedded Mode Means
926

1027
Embedded mode runs migrations directly against a local SharpCoreDB engine instance in the same process.
@@ -37,6 +54,43 @@ builder.Services.AddSharpCoreDBFluentMigrator(runner =>
3754
});
3855
```
3956

57+
### What must be registered for embedded execution
58+
59+
`AddSharpCoreDBFluentMigrator(...)` registers the FluentMigrator processor integration, but it does **not** force a remote client path.
60+
61+
Embedded execution works when the DI container can resolve one of these local execution sources:
62+
63+
- `IDatabase`
64+
- `DbConnection`
65+
66+
That means this is valid embedded usage:
67+
68+
```csharp
69+
using FluentMigrator.Runner;
70+
using SharpCoreDB.Extensions.Extensions;
71+
using SharpCoreDB.Interfaces;
72+
73+
builder.Services.AddSingleton<IDatabase>(database);
74+
builder.Services.AddSharpCoreDBFluentMigrator(runner =>
75+
{
76+
runner.ScanIn(typeof(Program).Assembly).For.Migrations();
77+
});
78+
```
79+
80+
A custom `ISharpCoreDbMigrationSqlExecutor` is optional and should only be added when you intentionally want to override SQL execution behavior.
81+
82+
### Why the API can look misleading at first
83+
84+
Users often inspect the package and notice that the only built-in `ISharpCoreDbMigrationSqlExecutor` implementation is the gRPC one.
85+
86+
That does **not** mean FluentMigrator is remote-only.
87+
88+
It means:
89+
90+
- the custom executor interface is an extension point
91+
- the current built-in custom executor implementation targets remote gRPC execution
92+
- embedded execution uses `IDatabase` or `DbConnection` directly instead of going through that custom interface
93+
4094
### Recommended startup execution
4195

4296
```csharp
@@ -119,11 +173,21 @@ public sealed class CreateUsersTableMigration : Migration
119173

120174
### Resolution order in `SharpCoreDbMigrationExecutor`
121175

176+
`SharpCoreDbMigrationExecutor` resolves the execution target in this order:
177+
122178
1. `ISharpCoreDbMigrationSqlExecutor` (custom override, if registered)
123-
2. `IDatabase` (embedded engine path)
179+
2. `IDatabase` (normal embedded engine path)
124180
3. `DbConnection` fallback
125181

126-
If you only use `AddSharpCoreDBFluentMigrator(...)` and embedded database DI, path (2) is used.
182+
This design allows one integration pipeline to support both local and remote execution models.
183+
184+
### What happens when no custom executor is registered
185+
186+
If you only use `AddSharpCoreDBFluentMigrator(...)` and register an embedded SharpCoreDB `IDatabase`, the executor uses the `IDatabase` path directly.
187+
188+
No gRPC client is created.
189+
No network hop is required.
190+
No remote server dependency is introduced.
127191

128192
### Transaction model
129193

@@ -151,8 +215,19 @@ Cause:
151215

152216
Fix:
153217
- Register SharpCoreDB database services before `AddSharpCoreDBFluentMigrator(...)`.
218+
- For embedded scenarios, prefer registering `IDatabase`.
219+
220+
### B) "I do not see an embedded migration SQL executor implementation"
221+
222+
Cause:
223+
- You are looking at the custom executor extension point and assuming every execution mode must implement it.
224+
225+
Fix:
226+
- Use `AddSharpCoreDBFluentMigrator(...)` for embedded mode.
227+
- Ensure `IDatabase` or `DbConnection` is registered.
228+
- Only use `AddSharpCoreDBFluentMigratorGrpc(...)` when you explicitly want remote execution over gRPC.
154229

155-
### B) Migration expression not supported
230+
### C) Migration expression not supported
156231

157232
Cause:
158233
- Expression translates to SQL not supported by the current engine behavior.
@@ -161,7 +236,7 @@ Fix:
161236
- Rewrite migration to equivalent supported SQL operations.
162237
- For advanced custom behavior, use explicit SQL in migration steps.
163238

164-
### C) Version table missing
239+
### D) Version table missing
165240

166241
Cause:
167242
- Processor not initialized in the migration pipeline.
@@ -174,6 +249,8 @@ Fix:
174249
## 8. Production Checklist (Embedded)
175250

176251
- [ ] `AddSharpCoreDBFluentMigrator(...)` is registered.
252+
- [ ] Embedded `IDatabase` or `DbConnection` is registered in DI.
253+
- [ ] No remote gRPC executor is registered unless intentionally overriding execution behavior.
177254
- [ ] Migrations are scanned from correct assembly.
178255
- [ ] Startup path executes `MigrateUp()` exactly once.
179256
- [ ] Backup/restore strategy exists before schema upgrades.

docs/migration/FLUENTMIGRATOR_SERVER_MODE_v1.7.0.md

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,21 @@
55

66
---
77

8+
## Quick Clarification
9+
10+
A common point of confusion is seeing the built-in gRPC implementation of `ISharpCoreDbMigrationSqlExecutor` and assuming that FluentMigrator only works against a remote SharpCoreDB server.
11+
12+
That assumption is incorrect.
13+
14+
SharpCoreDB supports both:
15+
16+
- **in-process server-host execution** via local `IDatabase` or `DbConnection`
17+
- **remote server execution** via `AddSharpCoreDBFluentMigratorGrpc(...)`
18+
19+
The custom executor interface is an extension point. The built-in gRPC executor is only the implementation for the remote path.
20+
21+
---
22+
823
## Decision Summary (Package Placement)
924

1025
FluentMigrator support is intentionally delivered through `SharpCoreDB.Extensions` for v1.7.0 because it is currently an integration/glue feature (DI registration + migration processor + execution adapters), not a standalone provider domain.
@@ -52,9 +67,15 @@ builder.Services.AddSharpCoreDBFluentMigrator(runner =>
5267

5368
This path resolves execution via:
5469

55-
- custom executor (if registered)
56-
- then `IDatabase`
57-
- then `DbConnection`
70+
1. custom executor (only if you explicitly register one)
71+
2. `IDatabase`
72+
3. `DbConnection`
73+
74+
### Important behavior note
75+
76+
In-process server-host mode does **not** require `ISharpCoreDbMigrationSqlExecutor`.
77+
78+
If the server host already has a local SharpCoreDB engine registered in DI, FluentMigrator runs locally in-process exactly like embedded mode. The fact that the process is a server does not automatically move it to the gRPC path.
5879

5980
---
6081

@@ -84,6 +105,12 @@ This wires:
84105
- `ISharpCoreDbMigrationSqlExecutor``SharpCoreDbGrpcMigrationSqlExecutor`
85106
- SQL execution through `SharpCoreDB.Client`
86107

108+
### When the custom executor is actually used
109+
110+
The built-in `SharpCoreDbGrpcMigrationSqlExecutor` is used only when you register `AddSharpCoreDBFluentMigratorGrpc(...)` or explicitly register a custom `ISharpCoreDbMigrationSqlExecutor` yourself.
111+
112+
If you do neither, SharpCoreDB falls back to local `IDatabase` / `DbConnection` resolution.
113+
87114
### Behavior notes
88115

89116
- `MigrateUp` / target version / rollback are available.
@@ -108,6 +135,7 @@ This wires:
108135

109136
- Preferred for single-service ownership deployments.
110137
- Run before endpoint binding/traffic readiness.
138+
- Uses the same local execution model as embedded mode when `IDatabase` or `DbConnection` is available.
111139

112140
### B) External migration job (remote)
113141

@@ -131,13 +159,24 @@ This wires:
131159
| `Rollback(steps)` |||
132160
| `PerformDBOperationExpression` with connection object | ✅ (if connection available) | ⚠️ Limited (`null` connection) |
133161
| Uses local `IDatabase` |||
162+
| Uses local `DbConnection` |||
163+
| Requires `ISharpCoreDbMigrationSqlExecutor` |||
134164
| Uses `SharpCoreDB.Client` network transport |||
135165

136166
---
137167

138168
## 7. Troubleshooting (Server Mode)
139169

140-
### A) Authentication/session failures in remote mode
170+
### A) "Why do I only see a gRPC migration executor implementation?"
171+
172+
Cause:
173+
- You are looking at the optional custom executor extension point rather than the full execution resolution model.
174+
175+
Fix:
176+
- For in-process server-host execution, use `AddSharpCoreDBFluentMigrator(...)` and register local `IDatabase` or `DbConnection`.
177+
- For remote execution, use `AddSharpCoreDBFluentMigratorGrpc(...)`.
178+
179+
### B) Authentication/session failures in remote mode
141180

142181
Symptoms:
143182
- gRPC errors on command execution
@@ -148,7 +187,7 @@ Fix:
148187
- verify target database exists
149188
- verify TLS/connectivity settings
150189

151-
### B) Migration works in embedded but fails in remote
190+
### C) Migration works in embedded but fails in remote
152191

153192
Cause:
154193
- remote mode has no direct `IDbConnection` callback object for DB-operation expressions.
@@ -157,7 +196,7 @@ Fix:
157196
- replace DB-operation callback with explicit migration SQL steps
158197
- avoid connection-dependent callback logic in remote pipelines
159198

160-
### C) Connection timeouts
199+
### D) Connection timeouts
161200

162201
Fix:
163202
- increase `SharpCoreDbGrpcMigrationOptions.CommandTimeoutMs`
@@ -191,6 +230,8 @@ migrationRunner.MigrateUp();
191230
## 9. Production Checklist (Server Mode)
192231

193232
- [ ] Correct mode selected: in-process vs remote gRPC.
233+
- [ ] In-process mode uses local `IDatabase` or `DbConnection` registration.
234+
- [ ] Remote mode uses `AddSharpCoreDBFluentMigratorGrpc(...)` intentionally.
194235
- [ ] Migration credentials scoped and secret-managed.
195236
- [ ] TLS enforced in all non-local environments.
196237
- [ ] Migration step integrated in deployment pipeline.

0 commit comments

Comments
 (0)