-
Notifications
You must be signed in to change notification settings - Fork 135
[Spec] Reconcile hydrated state against reality on rad startup
#12871
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
c002a98
d2d7099
4f4af5d
37324ea
644be73
a08e2f4
1af3ea0
611d6f2
80a8e2e
b4a63e4
497c945
3da0bcb
4fe9a67
a197e91
abf4868
9276b99
a6407e3
d562f3e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,10 +18,17 @@ package startup | |
|
|
||
| import ( | ||
| "context" | ||
| "errors" | ||
| "fmt" | ||
|
|
||
| "github.com/radius-project/radius/pkg/azure/tokencredentials" | ||
| "github.com/radius-project/radius/pkg/cli/controlplane" | ||
| "github.com/radius-project/radius/pkg/cli/output" | ||
| "github.com/radius-project/radius/pkg/cli/pgbackup" | ||
| "github.com/radius-project/radius/pkg/cli/tfstate" | ||
| "github.com/radius-project/radius/pkg/cli/workspaces" | ||
| corerpv20250801preview "github.com/radius-project/radius/pkg/corerp/api/v20250801preview" | ||
| "github.com/radius-project/radius/pkg/sdk" | ||
| ) | ||
|
|
||
| // ControlPlaneScaler scales the database-backed control-plane deployments to zero and back, so | ||
|
|
@@ -53,6 +60,31 @@ type StateRestoreClient interface { | |
|
|
||
| // RestoreTerraform re-creates the Terraform state Secrets from stateDir. | ||
| RestoreTerraform(ctx context.Context, kubeContext, namespace, stateDir string) error | ||
|
|
||
| // ReconcileHydratedState invokes the reconcile custom action on every application in the | ||
| // workspace's plane. It is best-effort: individual per-application failures are recorded in | ||
| // the returned reports but never propagate as a fatal error. Called by 'rad startup' after | ||
| // ScaleUp so subsequent commands see reality-checked state. | ||
| // | ||
| // A non-nil error is returned only when the pass could not begin at all (for example, the | ||
| // workspace's control plane is unreachable). Callers should treat such errors as advisory and | ||
| // still return success from the outer startup command. | ||
| ReconcileHydratedState(ctx context.Context, workspace *workspaces.Workspace) ([]ApplicationReconcileReport, error) | ||
| } | ||
|
|
||
| // ApplicationReconcileReport captures the per-application outcome of ReconcileHydratedState. One | ||
| // entry is produced for every application the reconcile pass attempted, whether it succeeded or | ||
| // not. | ||
| type ApplicationReconcileReport struct { | ||
| // Name is the application resource name (not the fully-qualified resource ID). | ||
| Name string | ||
| // ResourceCount is the number of child resources the reconcile handler reported an outcome | ||
| // for. Zero when the reconcile handler is still a stub, when the application has no | ||
| // non-terminal children, or when the reconcile call itself failed. | ||
| ResourceCount int | ||
| // Err is set when the reconcile call for this application failed. The pass continues to the | ||
| // next application regardless. | ||
| Err error | ||
| } | ||
|
|
||
| // defaultStateRestoreClient is the production implementation. | ||
|
|
@@ -78,3 +110,81 @@ func (defaultStateRestoreClient) RestoreTerraform(ctx context.Context, kubeConte | |
| } | ||
| return client.Restore(ctx, stateDir) | ||
| } | ||
|
|
||
| // ReconcileHydratedState lists every application in the workspace's plane and POSTs the | ||
| // Radius.Core/applications 'reconcile' custom action on each. Reports are aggregated across | ||
| // pagination and returned to the caller. | ||
| func (defaultStateRestoreClient) ReconcileHydratedState(ctx context.Context, workspace *workspaces.Workspace) ([]ApplicationReconcileReport, error) { | ||
| if workspace == nil { | ||
| return nil, errors.New("workspace is required") | ||
| } | ||
|
|
||
| connection, err := workspace.Connect(ctx) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to connect to workspace: %w", err) | ||
| } | ||
|
|
||
| clientOptions := sdk.NewClientOptions(connection) | ||
| factory, err := corerpv20250801preview.NewClientFactory(&tokencredentials.AnonymousCredential{}, clientOptions) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to build Radius.Core client factory: %w", err) | ||
| } | ||
| applications := factory.NewApplicationsClient() | ||
|
|
||
| // Collect application names first so a stalled reconcile does not stall the LIST. | ||
| names, err := listApplicationNames(ctx, applications, workspace.Scope) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to list applications for reconcile: %w", err) | ||
| } | ||
|
|
||
| reports := make([]ApplicationReconcileReport, 0, len(names)) | ||
| for _, name := range names { | ||
| report := ApplicationReconcileReport{Name: name} | ||
| resp, err := applications.Reconcile(ctx, workspace.Scope, name, corerpv20250801preview.ReconcileRequest{}, nil) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue(operations,blocking): Bound each application reconcile request This call inherits the command context without a deadline. A stalled Core RP enumeration or application request can prevent |
||
| if err != nil { | ||
| report.Err = err | ||
| } else { | ||
| report.ResourceCount = len(resp.Resources) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue(operations,blocking): Preserve skipped and unchanged outcomes in startup output The CLI keeps only |
||
| } | ||
| reports = append(reports, report) | ||
| } | ||
| return reports, nil | ||
| } | ||
|
|
||
| // listApplicationNames walks the paginated ListByScope response for `scope` and returns the | ||
| // application resource names. Nil entries and entries without a Name are skipped. | ||
| func listApplicationNames(ctx context.Context, client *corerpv20250801preview.ApplicationsClient, scope string) ([]string, error) { | ||
| pager := client.NewListByScopePager(scope, &corerpv20250801preview.ApplicationsClientListByScopeOptions{}) | ||
| var names []string | ||
| for pager.More() { | ||
| page, err := pager.NextPage(ctx) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| for _, app := range page.Value { | ||
| if app == nil || app.Name == nil { | ||
| continue | ||
| } | ||
| names = append(names, *app.Name) | ||
| } | ||
| } | ||
| return names, nil | ||
| } | ||
|
|
||
| // logReconcileReports emits one line per application, and a summary line if any application | ||
| // failed. Used by rad startup's ReconcileHydratedState stage to surface outcomes in the workflow | ||
| // log. | ||
| func logReconcileReports(out output.Interface, reports []ApplicationReconcileReport) { | ||
| failed := 0 | ||
| for _, r := range reports { | ||
| if r.Err != nil { | ||
| failed++ | ||
| out.LogInfo(" reconcile %s: failed (%s)", r.Name, r.Err.Error()) | ||
| continue | ||
| } | ||
| out.LogInfo(" reconcile %s: reconciled %d resource(s)", r.Name, r.ResourceCount) | ||
| } | ||
| if failed > 0 { | ||
| out.LogInfo("Reconcile completed with %d/%d application failures; continuing.", failed, len(reports)) | ||
| } | ||
| } | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
todo(non-blocking): Update the living architecture documentation
This post-restore pass changes the state-archive lifecycle and adds a generic state-mutating Dynamic RP action. Update
docs/architecture/state-archive.mdwith the reconciliation stage anddocs/architecture/dynamic-rp.mdwith the/reconcilepath, Kubernetes clients, and failure behavior.