[cherry-pick][1.44]Reconfigure Linseed and linseed user when migrating from multi-index … - #5205
[cherry-pick][1.44]Reconfigure Linseed and linseed user when migrating from multi-index …#5205asincu wants to merge 2 commits into
Conversation
…to single-index (tigera#5132) * (bootstrap): Add knob to reconfigure Linseed indices for a single tenant cluster This will be set operator bootstrap config map and enabled when migrating from multi-index format to single-index format. Linseed will reconfigure its environment variables to set the correct backend for the indices it is using and also set the base index name used for Cloud. * (logstorage-users): Provision Elasticsearch users from the operator while migrating to single-index storage Single-tenant clusters migrating to single-index storage need Linseed to hold RBAC for the new calico_* indices. es-kube-controllers cannot grant that, so during the migration the operator's log-storage users controller takes over user provisioning and es-kube-controllers stops running its elasticsearch configuration controller. - Run the users controller in single-tenant mode when IndexMigration is set, building the tenant configuration from the cloud config ConfigMap since single-tenant clusters have no Tenant resource. - Name the single-tenant Linseed and Dashboards users the way es-kube-controllers named them (<name>-<tenantID>-secure), and repoint existing credential secrets at those users while keeping their passwords, so credentials provisioned before the migration keep resolving. - Declare the standard single-index names on the Tenant that CloudConfig.ToTenant builds, gated on the caller opting in, so that clusters which are not migrating keep falling back to their existing index names. Sort the declared indices, as they are generated from a map. - Report the users TigeraStatus in the log-storage conditions aggregate while migrating. * (logstorage-conditions): Stop the log-storage conditions controller from writing on every reconcile updateConditions built its result by ranging over the desiredConditions map, so the order of LogStorage.Status.Conditions was randomized on every reconcile. Conditions is an atomic list, so a reorder is a real change to the stored object: each reconcile bumped the resourceVersion, and since this controller also watches LogStorage, that re-enqueued itself. The write loop ran continuously, and reconciles fired faster than the informer cache could converge - so reconciles read a stale tigera-secure and their status updates were rejected with "the object has been modified". Sort the conditions by type so the stored list is stable - an unchanged reconcile then computes a list identical to the stored one, which the API server discards without bumping the resourceVersion - and requeue instead of erroring when an update does hit a conflict. * (fix): Use CalicoEnterprise instead of TigeraEnterprise * (logstorage-users): Provision Elasticsearch users for all single tenant clusters Calico Cloud single-tenant clusters had their Elasticsearch users provisioned by es-kube-controllers, and the operator only took over while migrating to single-index storage. Take over for all of them, and grant Linseed access to the indices its cluster actually stores data in rather than to both name formats. - Run the log-storage users controller for every Calico Cloud single-tenant cluster, and stop es-kube-controllers running its elasticsearch configuration controller there so that it does not overwrite the users we own. Wait for the cloud config ConfigMap rather than erroring when it is not there yet. - Derive the Linseed role's index privileges from the tenant: the declared base index names when the cluster stores data in single-index format, the multi-index names otherwise - dropping the tenant qualifier for clusters on their own Elasticsearch, whose indices do not carry it. - Report the users TigeraStatus in the log-storage conditions aggregate for Calico Cloud rather than only while migrating. - Rename the index migration knob to USE_SINGLE_INDEX / UseSingleIndex, matching the naming already used by the linseed controller and render code. - Move CloudStandardIndices out of the API module and unexport it, as it is only consumed when building the single-tenant Tenant from the cloud config. * (logstorage-users): Address review feedback on Linseed index privileges - Skip indices with an empty base index name when building the Linseed role's index privileges. A Tenant declaring an index without a base name - whether misconfigured, or carrying a DataType added later without a mapping - would otherwise be wildcarded into "*", granting Linseed access to every index in Elasticsearch. Fall back to Linseed's default calico_ names when no usable base index name remains, which is what a Tenant declaring no indices at all already got. - Fix a comment on the single-tenant Linseed backend, which described the branch as migrating to multi-tenant style indices when it is gated by UseSingleIndex and migrating to single-index storage. * (fix): grant Linseed access to the policy activity index Policy activity is only ever stored in single-index format, so it is named outside the tigera_secure_ee_ pattern even on clusters that have not moved to single-index storage. The multi-index role we provision granted only that pattern, and since the role name matches the user name, putting it replaced the definition es-kube-controllers wrote - which did include the index. Linseed was then denied indices:admin/aliases/get when ingesting policy activity logs. Grant calico_policy_activity* alongside the multi-index pattern. The wildcard covers both Linseed's default index name and the calico_policy_activity_standard name pinned for clusters sharing an external Elasticsearch. * (fix): mark operator-provisioned Elasticsearch users as system users es-kube-controllers' authorization controller sweeps Elasticsearch on every resync and deletes any user that is neither marked with the "system:serviceaccount" full_name nor present in its OIDC user cache. The operator's User struct had no full_name field at all, so the Linseed and Dashboards installer users it provisions were created with an empty one and swept within a resync period, then recreated on the next reconcile - leaving the user flapping while both components are enabled. Add FullName to User, set it on both users, and send full_name in the create request. It is only sent when non-empty: the request replaces the user document, so sending it empty would strip the marker off a user that es-kube-controllers had already created with it. * (test): Make WithStandardIndices idempotent and cover it WithStandardIndices appended an entry for every data type without checking what the Tenant already declared, so applying it more than once - or alongside an option that declares its own indices - duplicated entries. Skip data types that are already present, letting an explicitly declared base index name win over the standard one rather than being duplicated or overridden. No current caller hits this: TenantFromCloudConfig always starts from a tenant with no indices, and every call site passes at most one option. This is hardening of a shared helper, not a behaviour change for any caller. Cover both cases - applying the option twice, and applying it after an option that already declared an index. Both specs fail against the previous implementation. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR updates the operator’s Calico Cloud log-storage flow to better support single-tenant clusters during (and after) migration from multi-index to single-index storage, including Linseed backend/index configuration, Elasticsearch user provisioning, and status-condition stability.
Changes:
- Introduces a
UseSingleIndexbootstrap/controller option and wires it through Linseed rendering to select the single-index backend and configure index base names. - Moves Elasticsearch user provisioning for Calico Cloud single-tenant clusters to the operator (and disables
es-kube-controllers’ elasticsearch configuration controller there), including backward-compatible user naming and credential-secret repointing. - Stabilizes LogStorage status conditions by sorting and avoids hot-looping on status update conflicts.
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| cmd/main.go | Reads USE_SINGLE_INDEX from bootstrap config and passes it into controller options. |
| pkg/common/discovery/discovery.go | Adds bootstrap discovery helper UseSingleIndex. |
| pkg/controller/options/options.go | Adds UseSingleIndex to controller options for wiring through controllers/rendering. |
| pkg/controller/logstorage/linseed/linseed_controller.go | Threads UseSingleIndex into cloud single-tenant tenant-building and Linseed render config. |
| pkg/controller/logstorage/linseed/linseed_controller_test.go | Updates controller shim setup to use the consolidated opts field. |
| pkg/render/logstorage/linseed/linseed.go | Adds UseSingleIndex rendering behavior to select backend + index env vars for single-tenant migrations. |
| pkg/controller/logstorage/users/users_controller.go | Runs users controller in cloud single-tenant mode; derives tenant config from ConfigMaps; provisions/repoints ES users and secrets. |
| pkg/controller/logstorage/users/users_controller_test.go | Adds coverage for single-tenant cloud user provisioning, secret repointing, and index privilege selection. |
| pkg/controller/logstorage/initializer/conditions_controller.go | Sorts status conditions and requeues on conflict to prevent reconcile hot loops; includes users status for Cloud single-tenant. |
| pkg/controller/logstorage/esutils/elasticsearch.go | Adjusts Linseed/Dashboards user naming, roles/index privileges, and marks operator users as system users (full_name). |
| pkg/controller/logstorage/esutils/elasticsearch_test.go | Adds tests for full_name handling and single-/multi-tenant role/index behavior. |
| pkg/enterprise/utils/cloudconfig.go | Adds GetClusterID helper and ClusterInfoConfigMap constant for cluster-id lookup. |
| pkg/enterprise/utils/tenant.go | Adds Tenant options (WithStandardIndices*) and deterministic index ordering when building a single-tenant Tenant from CloudConfig. |
| pkg/enterprise/utils/tenant_test.go | Adds tests for TenantFromCloudConfig options and idempotent standard-index declaration. |
| pkg/enterprise/kubecontrollers/kubecontrollers.go | Disables elasticsearchconfiguration controller in Calico Cloud to avoid user overwrites. |
| pkg/enterprise/kubecontrollers/es_kube_controllers_test.go | Adds test asserting elasticsearchconfiguration is disabled in Cloud. |
| pkg/controller/logstorage/dashboards/dashboards_controller.go | Updates comment to reflect cloud credentials provisioning source. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 19 out of 19 changed files in this pull request and generated no new comments.
Suppressed comments (2)
pkg/controller/logstorage/users/users_controller.go:88
- The controller is now disabled for all non-Cloud installs, but LogStorageConditions still expects the log-storage users TigeraStatus whenever MultiTenant is true. If MultiTenant can be enabled outside Calico Cloud, this will leave LogStorage stuck degraded because the users controller (and its TigeraStatus) never runs. Consider gating on
opts.Cloud || opts.MultiTenant(or adjusting the conditions expectation) so multi-tenant non-Cloud clusters continue to provision users / report status.
if !opts.Cloud {
// The operator creates users for cloud clusters. Anywhere
// else, user creation is handled by es-kube-controllers instead.
return nil
}
pkg/enterprise/utils/tenant_test.go:25
- This new Ginkgo spec file is in
package utils, but this directory’s suite runner (utils_suite_test.go) is inpackage utils_test. As-is, these specs won’t run as part of the existing suite (you’ll effectively have a second test package with noRunSpecs). Consider switching this file topackage utils_test(importingpkg/enterprise/utils) or adding a suite runner for theutilspackage.
package utils
import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
v1 "github.com/tigera/operator/api/v1"
"github.com/tigera/operator/pkg/enterprise/cloudconfig"
)
…to single-index (#5132)
This will be set operator bootstrap config map and enabled when migrating from multi-index format to single-index format. Linseed will reconfigure its environment variables to set the correct backend for the indices it is using and also set the base index name used for Cloud.
Single-tenant clusters migrating to single-index storage need Linseed to hold RBAC for the new calico_* indices. es-kube-controllers cannot grant that, so during the migration the operator's log-storage users controller takes over user provisioning and es-kube-controllers stops running its elasticsearch configuration controller.
updateConditions built its result by ranging over the desiredConditions map, so the order of LogStorage.Status.Conditions was randomized on every reconcile. Conditions is an atomic list, so a reorder is a real change to the stored object: each reconcile bumped the resourceVersion, and since this controller also watches LogStorage, that re-enqueued itself. The write loop ran continuously, and reconciles fired faster than the informer cache could converge - so reconciles read a stale tigera-secure and their status updates were rejected with "the object has been modified".
Sort the conditions by type so the stored list is stable - an unchanged reconcile then computes a list identical to the stored one, which the API server discards without bumping the resourceVersion - and requeue instead of erroring when an update does hit a conflict.
(fix): Use CalicoEnterprise instead of TigeraEnterprise
(logstorage-users): Provision Elasticsearch users for all single tenant clusters
Calico Cloud single-tenant clusters had their Elasticsearch users provisioned by es-kube-controllers, and the operator only took over while migrating to single-index storage. Take over for all of them, and grant Linseed access to the indices its cluster actually stores data in rather than to both name formats.
Policy activity is only ever stored in single-index format, so it is named outside the tigera_secure_ee_ pattern even on clusters that have not moved to single-index storage. The multi-index role we provision granted only that pattern, and since the role name matches the user name, putting it replaced the definition es-kube-controllers wrote - which did include the index. Linseed was then denied indices:admin/aliases/get when ingesting policy activity logs.
Grant calico_policy_activity* alongside the multi-index pattern. The wildcard covers both Linseed's default index name and the calico_policy_activity_standard name pinned for clusters sharing an external Elasticsearch.
es-kube-controllers' authorization controller sweeps Elasticsearch on every resync and deletes any user that is neither marked with the "system:serviceaccount" full_name nor present in its OIDC user cache. The operator's User struct had no full_name field at all, so the Linseed and Dashboards installer users it provisions were created with an empty one and swept within a resync period, then recreated on the next reconcile - leaving the user flapping while both components are enabled.
Add FullName to User, set it on both users, and send full_name in the create request. It is only sent when non-empty: the request replaces the user document, so sending it empty would strip the marker off a user that es-kube-controllers had already created with it.
WithStandardIndices appended an entry for every data type without checking what the Tenant already declared, so applying it more than once - or alongside an option that declares its own indices - duplicated entries. Skip data types that are already present, letting an explicitly declared base index name win over the standard one rather than being duplicated or overridden.
No current caller hits this: TenantFromCloudConfig always starts from a tenant with no indices, and every call site passes at most one option. This is hardening of a shared helper, not a behaviour change for any caller.
Cover both cases - applying the option twice, and applying it after an option that already declared an index. Both specs fail against the previous implementation.
Description
Release Note
For PR author
make gen-filesmake gen-versionsFor PR reviewers
A note for code reviewers - all pull requests must have the following:
kind/bugif this is a bugfix.kind/enhancementif this is a a new feature.enterpriseif this PR applies to Calico Enterprise only.