Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
12a54fc
Adding multigrescluster and tablegroup controller
fernando-villalba Dec 17, 2025
e0c6b2a
fix: decouple GlobalTopoServer and MultiAdmin CoreTemplate resolution
fernando-villalba Dec 18, 2025
736709a
controller: add missing documentation comments
fernando-villalba Dec 18, 2025
29963bd
fix(controller): resolve GlobalTopoServer from templates correctly
fernando-villalba Dec 18, 2025
f99839b
fix(controller): enforce 50-char limit on TableGroup names
fernando-villalba Dec 18, 2025
035bc79
test(controller): improve MultigresCluster coverage and update valida…
fernando-villalba Dec 18, 2025
e69a2ce
test: verify Cell wiring in MultigresCluster controller
fernando-villalba Dec 18, 2025
e4ad00d
fix: enable MultiOrch placement defaulting
fernando-villalba Dec 19, 2025
5b5c7b2
fix: enforce existence for explicit templates
fernando-villalba Dec 19, 2025
2cc5b10
Various Go style improvements
fernando-villalba Dec 19, 2025
568d7e6
refactor(cluster-handler): centralize defaulting constants for webhoo…
fernando-villalba Dec 21, 2025
3a40dfb
Correcting the previous premature commit
fernando-villalba Dec 22, 2025
7e52849
refactor: split global component reconciliation logic
fernando-villalba Dec 22, 2025
48a1ec0
refactor: extract deletion handling to handleDelete
fernando-villalba Dec 22, 2025
c86eb45
test: use map for table-driven tests in MultigresCluster controller
fernando-villalba Dec 22, 2025
9d8f7ce
test: enable parallel execution for MultigresCluster controller tests
fernando-villalba Dec 22, 2025
60b96bb
test: adopt t.Context() for better test lifecycle management
fernando-villalba Dec 22, 2025
8658528
test: enforce strict error handling in MultigresCluster controller tests
fernando-villalba Dec 22, 2025
e21681f
test: use testing.TB interface in controller test helpers
fernando-villalba Dec 22, 2025
b4f9fdb
test: split MultigresCluster reconcile tests into success and failure…
fernando-villalba Dec 22, 2025
508c7ed
test: rename cluster field to multigrescluster in controller tests
fernando-villalba Dec 22, 2025
c661e22
test: introduce preReconcileUpdate hook to simplify test fixtures
fernando-villalba Dec 22, 2025
55ad5ac
test: remove unused setupFunc and simplify test fixtures
fernando-villalba Dec 22, 2025
a04a7f2
test: simplify client initialization in failure tests
fernando-villalba Dec 22, 2025
b189f6c
test: refactor TableGroup controller tests for consistency and hygiene
fernando-villalba Dec 22, 2025
4ba9e0a
Updating API for the branch with latest changes from main
fernando-villalba Dec 22, 2025
c57aef6
Adding integration tests and updating the API spec to adjust CEL budg…
fernando-villalba Dec 22, 2025
b8a050f
fix: optimize CEL validation cost and refactor controller tests
fernando-villalba Dec 22, 2025
fbd230a
refactor(controller): remove unnecessary setupFunc from cluster tests
fernando-villalba Dec 22, 2025
cd48659
refactor(controller): simplify test setup and fix deletion handling i…
fernando-villalba Dec 22, 2025
2dc9e48
refactor(controller): reduce test boilerplate by defaulting existing …
fernando-villalba Dec 22, 2025
abf2a39
refactor(controller): use simple equality checks in cluster tests
fernando-villalba Dec 22, 2025
fc18a00
refactor(controller): default existingObjects in cluster tests to red…
fernando-villalba Dec 22, 2025
9861b16
test(controller): remove redundant 'No Global Topo Config' test case
fernando-villalba Dec 22, 2025
bb9d2b8
test(controller): remove redundant 'No Global Topo Config' test case
fernando-villalba Dec 22, 2025
2562d8c
refactor(controller): use explicit flag for skipping cluster creation…
fernando-villalba Dec 22, 2025
8a14d5f
refactor(controller): use idiomatic short variable declaration in tests
fernando-villalba Dec 22, 2025
3694e40
refactor(controller): adhere to go idioms and fix regression in tests
fernando-villalba Dec 22, 2025
e08df36
refactor(controller): standardize tablegroup controller tests
fernando-villalba Dec 22, 2025
bbe8dad
test: refactor cluster-handler integration tests and standardize cont…
fernando-villalba Dec 23, 2025
ed96ac6
ci: fix YAML indentation in reusable coverage workflow
fernando-villalba Dec 23, 2025
0b6077a
ci: fix build dependencies and coverage workflow syntax
fernando-villalba Dec 23, 2025
19fdc63
Fixing lint issues
fernando-villalba Dec 23, 2025
5c51f87
Reverting go.sum from main to prevent build failure and updating .git…
fernando-villalba Dec 23, 2025
4134a43
Adding go work config for test coverage to fix build and ensure local…
fernando-villalba Dec 23, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 49 additions & 23 deletions .github/workflows/_reusable-test-coverage.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ jobs:
fetch-depth: 0 # Fetch all history for changed-modules detection

- name: Fetch base branch for comparison
run: |
run: |
TARGET_BRANCH="${{ github.base_ref || 'main' }}"
echo "Fetching comparison target: $TARGET_BRANCH"
git fetch origin ${TARGET_BRANCH}:refs/remotes/origin/${TARGET_BRANCH}
Expand Down Expand Up @@ -93,6 +93,11 @@ jobs:
echo "Previous test coverage: Not found"
fi

## Setup Workspace for Tooling
# This forces Go to look at local directories for modules instead of trying to fetch them from online.
go work init
go work use -r .

## Step 2. Check new coverage
# Get into directory so that go tool cover can work
go tool cover -func=coverage/combined.out > /tmp/coverage.txt
Expand All @@ -108,15 +113,16 @@ jobs:
# change is made, the previous test reports will be minimised, leaving the
# only relevant comment.
- name: Check and report
uses: actions/github-script@v6 # Based on Node.js v16
if: always() &&
github.event_name == 'pull_request'
uses: actions/github-script@v6
if: always() && github.event_name == 'pull_request'
env:
COVERAGE_THRESHOLD: ${{ inputs.COVERAGE_THRESHOLD }}
with:
retries: 3
script: |
const fs = require('fs/promises')

// 1. Retrieve existing bot comments for the PR
// 1. Retrieve existing bot comments
const { data: comments } = await github.rest.issues.listComments({
...context.repo,
issue_number: context.issue.number,
Expand All @@ -126,23 +132,44 @@ jobs:
comment.body.includes('Go Test Coverage Report')
});

// 2. Prepare comment
const report = await fs.readFile('/tmp/coverage.txt')
const overallStatus =
${{ inputs.COVERAGE_THRESHOLD }} > ${{ env.NEW_COVERAGE }} ?
"❌ FAIL: Coverage less than threshold of `${{ inputs.COVERAGE_THRESHOLD }}`" :
${{ env.PREV_COVERAGE || '0' }} > ${{ env.NEW_COVERAGE }} ?
"❌ FAIL: Coverage less than the previous run" :
"✅ PASS"
// 2. Safe Value Parsing
const threshold = parseFloat(process.env.COVERAGE_THRESHOLD) || 0;
const newCovStr = process.env.NEW_COVERAGE;
const prevCovStr = process.env.PREV_COVERAGE;

const newCov = parseFloat(newCovStr); // NaN if undefined/empty
const prevCov = parseFloat(prevCovStr) || 0;

let overallStatus = "⚠️ SKIPPED (Build Failed?)";

// Only calculate status if we actually have coverage data
if (!isNaN(newCov)) {
if (threshold > newCov) {
overallStatus = `❌ FAIL: Coverage (${newCov}%) < Threshold (${threshold}%)`;
} else if (prevCov > newCov) {
overallStatus = `❌ FAIL: Coverage decreased (${prevCov}% -> ${newCov}%)`;
} else {
overallStatus = "✅ PASS";
}
}

// 3. Read Report or Default Message
let reportContent = "No coverage report generated (Build likely failed).";
try {
reportContent = await fs.readFile('/tmp/coverage.txt', 'utf8');
} catch (e) {
console.log("Could not read coverage file:", e.message);
}

const comment = `### 🔬 Go Test Coverage Report

#### Summary

| Coverage Type | Result |
| ---------------------- | -------------------------------------- |
| Threshold | ${{ inputs.COVERAGE_THRESHOLD }}% |
| Previous Test Coverage | ${{ env.PREV_COVERAGE || 'Unknown' }}% |
| New Test Coverage | ${{ env.NEW_COVERAGE }}% |
| Threshold | ${threshold}% |
| Previous Test Coverage | ${prevCovStr || 'Unknown'}% |
| New Test Coverage | ${newCovStr || 'Unknown'}% |

#### Status

Expand All @@ -153,14 +180,14 @@ jobs:
<details><summary>Show New Coverage</summary>

\`\`\`
${report}\`\`\`
${reportContent}
\`\`\`

</details>
`;

// 3. If there are any old comments, minimize all of them first.
// 4. Minimize old comments
for (const botComment of botComments) {
core.notice("There was an old comment found in the PR, minimizing it.")
const query = `mutation {
minimizeComment(input: {classifier: OUTDATED, subjectId: "${botComment.node_id}"}) {
clientMutationId
Expand All @@ -169,7 +196,7 @@ jobs:
await github.graphql(query)
}

// 4. Create a comment with the coverage report
// 5. Create new comment
github.rest.issues.createComment({
...context.repo,
issue_number: context.issue.number,
Expand All @@ -178,7 +205,7 @@ jobs:

# Exit with non-zero value if the test coverage has decreased or not
# reached the threshold.
- name: Check coverage status
- name: Check coverage status
run: |
echo "Coverage Threshold: ${{ inputs.COVERAGE_THRESHOLD }}%"
echo "Previous test coverage: ${{ env.PREV_COVERAGE || 'Unknown' }}%"
Expand Down Expand Up @@ -226,5 +253,4 @@ jobs:
path: |
~/.cache/coverage.txt
~/.cache/go-build
~/go/pkg/mod

~/go/pkg/mod
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,7 @@ bin/

# kind local development
kubeconfig.yaml


# MacOS
.DS_Store
14 changes: 11 additions & 3 deletions api/v1alpha1/cell_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,11 @@ type CellSpec struct {

// TopoServer defines the local topology config.
// +optional
TopoServer LocalTopoServerSpec `json:"topoServer,omitempty"`
TopoServer *LocalTopoServerSpec `json:"topoServer,omitempty"`

// AllCells list for discovery.
// +optional
// +kubebuilder:validation:MaxItems=100
// +kubebuilder:validation:MaxItems=50
AllCells []CellName `json:"allCells,omitempty"`

// TopologyReconciliation flags.
Expand All @@ -71,7 +71,10 @@ type CellSpec struct {

// TopologyReconciliation defines flags for the cell controller.
type TopologyReconciliation struct {
// RegisterCell indicates if the cell should register itself in the topology.
RegisterCell bool `json:"registerCell"`

// PrunePoolers indicates if unused poolers should be removed.
PrunePoolers bool `json:"prunePoolers"`
}

Expand All @@ -85,8 +88,13 @@ type CellStatus struct {
// +optional
Conditions []metav1.Condition `json:"conditions,omitempty"`

GatewayReplicas int32 `json:"gatewayReplicas"`
// GatewayReplicas is the total number of gateway pods.
GatewayReplicas int32 `json:"gatewayReplicas"`

// GatewayReadyReplicas is the number of gateway pods that are ready.
GatewayReadyReplicas int32 `json:"gatewayReadyReplicas"`

// GatewayServiceName is the DNS name of the gateway service.
// +kubebuilder:validation:MaxLength=253
GatewayServiceName string `json:"gatewayServiceName,omitempty"`
}
Expand Down
4 changes: 2 additions & 2 deletions api/v1alpha1/common_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,13 @@ type StatelessSpec struct {

// PodAnnotations are annotations to add to the pods.
// +optional
// +kubebuilder:validation:MaxProperties=64
// +kubebuilder:validation:MaxProperties=20
// +kubebuilder:validation:XValidation:rule="self.all(k, size(k) < 64 && size(self[k]) < 256)",message="annotation keys must be <64 chars and values <256 chars"
PodAnnotations map[string]string `json:"podAnnotations,omitempty"`

// PodLabels are additional labels to add to the pods.
// +optional
// +kubebuilder:validation:MaxProperties=64
// +kubebuilder:validation:MaxProperties=20
// +kubebuilder:validation:XValidation:rule="self.all(k, size(k) < 64 && size(self[k]) < 64)",message="label keys and values must be <64 chars"
PodLabels map[string]string `json:"podLabels,omitempty"`
}
Expand Down
16 changes: 8 additions & 8 deletions api/v1alpha1/multigrescluster_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,15 @@ type MultigresClusterSpec struct {
// +optional
// +listType=map
// +listMapKey=name
// +kubebuilder:validation:MaxItems=100
// +kubebuilder:validation:MaxItems=50
Cells []CellConfig `json:"cells,omitempty"`

// Databases defines the logical databases, table groups, and sharding.
// +optional
// +listType=map
// +listMapKey=name
// +kubebuilder:validation:XValidation:rule="self.filter(x, has(x.default) && x.default).size() <= 1",message="only one database can be marked as default"
// +kubebuilder:validation:MaxItems=500
// +kubebuilder:validation:MaxItems=50
Databases []DatabaseConfig `json:"databases,omitempty"`
}

Expand Down Expand Up @@ -218,7 +218,7 @@ type DatabaseConfig struct {
// +listType=map
// +listMapKey=name
// +kubebuilder:validation:XValidation:rule="self.filter(x, has(x.default) && x.default).size() <= 1",message="only one tablegroup can be marked as default"
// +kubebuilder:validation:MaxItems=100
// +kubebuilder:validation:MaxItems=20
TableGroups []TableGroupConfig `json:"tablegroups,omitempty"`
}

Expand All @@ -237,7 +237,7 @@ type TableGroupConfig struct {
// +optional
// +listType=map
// +listMapKey=name
// +kubebuilder:validation:MaxItems=128
// +kubebuilder:validation:MaxItems=32
Shards []ShardConfig `json:"shards,omitempty"`
}

Expand Down Expand Up @@ -272,7 +272,7 @@ type ShardOverrides struct {

// Pools overrides. Keyed by pool name.
// +optional
// +kubebuilder:validation:MaxProperties=32
// +kubebuilder:validation:MaxProperties=8
// +kubebuilder:validation:XValidation:rule="self.all(key, size(key) < 63)",message="pool names must be < 63 chars"
Pools map[string]PoolSpec `json:"pools,omitempty"`
}
Expand All @@ -285,7 +285,7 @@ type ShardInlineSpec struct {

// Pools configuration. Keyed by pool name.
// +optional
// +kubebuilder:validation:MaxProperties=32
// +kubebuilder:validation:MaxProperties=8
// +kubebuilder:validation:XValidation:rule="self.all(key, size(key) < 63)",message="pool names must be < 63 chars"
Pools map[string]PoolSpec `json:"pools,omitempty"`
}
Expand All @@ -305,13 +305,13 @@ type MultigresClusterStatus struct {
Conditions []metav1.Condition `json:"conditions,omitempty"`
// Cells status summary.
// +optional
// +kubebuilder:validation:MaxProperties=100
// +kubebuilder:validation:MaxProperties=50
// +kubebuilder:validation:XValidation:rule="self.all(key, size(key) < 63)",message="cell names must be < 63 chars"
Cells map[string]CellStatusSummary `json:"cells,omitempty"`

// Databases status summary.
// +optional
// +kubebuilder:validation:MaxProperties=500
// +kubebuilder:validation:MaxProperties=50
// +kubebuilder:validation:XValidation:rule="self.all(key, size(key) < 63)",message="database names must be < 63 chars"
Databases map[string]DatabaseStatusSummary `json:"databases,omitempty"`
}
Expand Down
31 changes: 22 additions & 9 deletions api/v1alpha1/shard_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ type MultiOrchSpec struct {
// Cells defines the list of cells where this MultiOrch should be deployed.
// If empty, it defaults to all cells where pools are defined.
// +optional
// +kubebuilder:validation:MaxItems=100
// +kubebuilder:validation:MaxItems=50
Cells []CellName `json:"cells,omitempty"`
}

Expand All @@ -49,7 +49,7 @@ type PoolSpec struct {

// Cells defines the list of cells where this Pool should be deployed.
// +optional
// +kubebuilder:validation:MaxItems=100
// +kubebuilder:validation:MaxItems=50
Cells []CellName `json:"cells,omitempty"`

// ReplicasPerCell is the desired number of pods PER CELL in this pool.
Expand Down Expand Up @@ -83,34 +83,44 @@ type PoolSpec struct {

// ShardSpec defines the desired state of Shard.
type ShardSpec struct {
// DatabaseName is the name of the logical database this shard belongs to.
// +kubebuilder:validation:MaxLength=63
DatabaseName string `json:"databaseName"`

// TableGroupName is the name of the table group this shard belongs to.
// +kubebuilder:validation:MaxLength=63
TableGroupName string `json:"tableGroupName"`

// ShardName is the specific identifier for this shard (e.g. "0").
// +kubebuilder:validation:MaxLength=63
ShardName string `json:"shardName"`

// Images required.
// Images defines the container images to be used by this shard (defined globally at MultigresCluster).
Images ShardImages `json:"images"`

// GlobalTopoServer reference.
// GlobalTopoServer is a reference to the global topology server.
GlobalTopoServer GlobalTopoServerRef `json:"globalTopoServer"`

// MultiOrch fully resolved spec.
// MultiOrch is the fully resolved configuration for the shard orchestrator.
MultiOrch MultiOrchSpec `json:"multiorch"`

// Pools fully resolved spec.
// +kubebuilder:validation:MaxProperties=32
// Pools is the map of fully resolved data pool configurations.
// +kubebuilder:validation:MaxProperties=8
// +kubebuilder:validation:XValidation:rule="self.all(key, size(key) < 63)",message="pool names must be < 63 chars"
Pools map[string]PoolSpec `json:"pools"`
}

// ShardImages defines the images required for a Shard.
type ShardImages struct {
// MultiOrch is the image for the shard orchestrator.
// +kubebuilder:validation:MaxLength=512
MultiOrch string `json:"multiorch"`

// MultiPooler is the image for the connection pooler sidecar.
// +kubebuilder:validation:MaxLength=512
MultiPooler string `json:"multipooler"`

// Postgres is the image for the postgres database.
// +kubebuilder:validation:MaxLength=512
Postgres string `json:"postgres"`
}
Expand All @@ -127,10 +137,13 @@ type ShardStatus struct {

// Cells is a list of cells this shard is currently deployed to.
// +optional
// +kubebuilder:validation:MaxItems=100
// +kubebuilder:validation:MaxItems=50
Cells []CellName `json:"cells,omitempty"`

OrchReady bool `json:"orchReady"`
// OrchReady indicates if the MultiOrch component is ready.
OrchReady bool `json:"orchReady"`

// PoolsReady indicates if all data pools are ready.
PoolsReady bool `json:"poolsReady"`
}

Expand Down
2 changes: 1 addition & 1 deletion api/v1alpha1/shardtemplate_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ type ShardTemplateSpec struct {
MultiOrch *MultiOrchSpec `json:"multiorch,omitempty"`

// +optional
// +kubebuilder:validation:MaxProperties=32
// +kubebuilder:validation:MaxProperties=8
// +kubebuilder:validation:XValidation:rule="self.all(key, size(key) < 63)",message="pool names must be < 63 chars"
Pools map[string]PoolSpec `json:"pools,omitempty"`
}
Expand Down
Loading