Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 17 additions & 0 deletions api/v1/tenant_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,23 @@ var DataTypes = map[DataType]string{
DataTypePolicyActivity: "ELASTIC_POLICY_ACTIVITY_BASE_INDEX_NAME",
}

var CloudStandardIndices = map[DataType]string{
DataTypeAlerts: "calico_alerts_standard",
DataTypeAuditLogs: "calico_auditlogs_standard",
DataTypeBGPLogs: "calico_bgplogs_standard",
DataTypeComplianceBenchmarks: "calico_compliance_benchmarks_results_standard",
DataTypeComplianceReports: "calico_compliance_reports_standard",
DataTypeComplianceSnapshots: "calico_compliance_snapshots_standard",
DataTypeDNSLogs: "calico_dnslogs_standard",
DataTypeFlowLogs: "calico_flowlogs_standard",
DataTypeL7Logs: "calico_l7logs_standard",
DataTypeRuntimeReports: "calico_runtime_reports_standard",
DataTypeThreatFeedsDomainSet: "calico_threatfeeds_domainnameset_standard",
DataTypeThreatFeedsIPSet: "calico_threatfeeds_ipset_standard",
DataTypeWAFLogs: "calico_waflogs_standard",
DataTypePolicyActivity: "calico_policy_activity_standard",
}

type TenantSpec struct {
// ID is the unique identifier for this tenant.
// +required
Expand Down
3 changes: 3 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -550,10 +550,12 @@ If a value other than 'all' is specified, the first CRD with a prefix of the spe
}

elasticIsMigrating := false
indexIsMigrating := false
useExternalElastic := discovery.UseExternalElastic(bootConfig)

if isCloudBuild() {
elasticIsMigrating = discovery.ElasticIsMigrating(bootConfig)
indexIsMigrating = discovery.IndexIsMigrating(bootConfig)
if !elasticIsMigrating {
if err := verifyElasticSearch(ctx, cs, useExternalElastic); err != nil {
setupLog.Error(err, "Elasticsearch configuration verification failed")
Expand All @@ -580,6 +582,7 @@ If a value other than 'all' is specified, the first CRD with a prefix of the spe
ElasticExternal: useExternalElastic,
Cloud: isCloudBuild(),
ESMigration: elasticIsMigrating,
IndexMigration: indexIsMigrating,
UseV3CRDs: v3CRDs,
APIDiscovery: apiDiscovery,
}
Expand Down
16 changes: 16 additions & 0 deletions pkg/common/discovery/discovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -308,3 +308,19 @@ func ElasticIsMigrating(config *corev1.ConfigMap) bool {
}
return false
}

// IndexIsMigrating returns true if this cluster is in the last phase of a migration to single-index
// storage, during which the operator must reconfigure Linseed to use the single-index names.
func IndexIsMigrating(config *corev1.ConfigMap) bool {
if config == nil {
return false
}

// Load the operator bootstrap configuration from its configmap.
if val, ok := config.Data["INDEX_MIGRATION"]; ok && val != "" {
if strings.ToLower(val) == "true" {
return true
}
}
return false
}
2 changes: 1 addition & 1 deletion pkg/controller/compliance/compliance_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,7 @@ func (r *ReconcileCompliance) Reconcile(ctx context.Context, request reconcile.R
r.status.SetDegraded(operatorv1.ResourceReadError, "Unable to read External Elasticsearch config map", err, reqLogger)
return reconcile.Result{}, err
}
tenant = cloudConfig.ToTenant()
tenant = cloudConfig.ToTenant(false)
}

reqLogger.V(3).Info("rendering components")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,7 @@ func (r *ReconcileIntrusionDetection) Reconcile(ctx context.Context, request rec
r.status.SetDegraded(operatorv1.ResourceReadError, "Failed to read cloud config", err, reqLogger)
return reconcile.Result{}, err
}
tenant = cloudConfig.ToTenant()
tenant = cloudConfig.ToTenant(false)
}

// Create a component handler to manage the rendered component.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ func (d DashboardsSubController) Reconcile(ctx context.Context, request reconcil
d.status.SetDegraded(operatorv1.ResourceReadError, "Failed to read cloud config", err, reqLogger)
return reconcile.Result{}, err
}
tenant = cloudConfig.ToTenant()
tenant = cloudConfig.ToTenant(false)
}

// Determine the host and port from the URL.
Expand Down Expand Up @@ -321,7 +321,8 @@ func (d DashboardsSubController) Reconcile(ctx context.Context, request reconcil

// Query the username and password this Dashboards Installer instance should use to authenticate with Elasticsearch.
// For multi-tenant systems, credentials are created by the elasticsearch users controller.
// For single-tenant system, these are created by es-kube-controllers.
// For single-tenant systems, these are created by es-kube-controllers, unless the cluster is
// migrating to single-index storage - then the users controller creates them as well.
key = types.NamespacedName{Name: dashboards.ElasticCredentialsSecret, Namespace: helper.InstallNamespace()}
credentials := corev1.Secret{}
if err = d.client.Get(ctx, key, &credentials); err != nil && !errors.IsNotFound(err) {
Expand Down
39 changes: 35 additions & 4 deletions pkg/controller/logstorage/initializer/conditions_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ package initializer
import (
"context"
"fmt"
"sort"
"time"

"k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
Expand All @@ -41,9 +43,10 @@ func AddConditionsController(mgr manager.Manager, opts options.ControllerOptions

// Create the reconciler
r := &LogStorageConditions{
client: mgr.GetClient(),
scheme: mgr.GetScheme(),
multiTenant: opts.MultiTenant,
client: mgr.GetClient(),
scheme: mgr.GetScheme(),
multiTenant: opts.MultiTenant,
indexMigration: opts.IndexMigration,
}

return ctrl.NewControllerManagedBy(mgr).
Expand All @@ -62,6 +65,10 @@ type LogStorageConditions struct {
client client.Client
scheme *runtime.Scheme
multiTenant bool

// indexMigration indicates that this single-tenant cluster is migrating to single-index storage,
// in which case the log-storage users controller runs and reports status.
indexMigration bool
}

func (r *LogStorageConditions) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) {
Expand All @@ -88,9 +95,22 @@ func (r *LogStorageConditions) Reconcile(ctx context.Context, request reconcile.
}

// Compare and update the current StatusCondition if there are any new changes
ls.Status.Conditions = updateConditions(currentConditions, desiredConditions)
conditions := updateConditions(currentConditions, desiredConditions)

// Skip the write if nothing changed. This controller watches LogStorage, so a no-op write would
// re-trigger it and spin: each reconcile would bump the resourceVersion and enqueue another one.
if equality.Semantic.DeepEqual(ls.Status.Conditions, conditions) {
return reconcile.Result{}, nil
}
ls.Status.Conditions = conditions

if err := r.client.Status().Update(ctx, ls); err != nil {
if errors.IsConflict(err) {
// The LogStorage was modified after we read it - our cached copy is stale. Requeue and
// recompute the conditions from the updated object instead of reporting an error.
reqLogger.V(1).Info("Conflict updating LogStorage status conditions, retrying")
return reconcile.Result{Requeue: true}, nil
}
log.WithValues("reason", err).Info("Failed to update LogStorage status conditions")
return reconcile.Result{}, err
}
Expand All @@ -112,6 +132,10 @@ func (r *LogStorageConditions) getDesiredConditions(ctx context.Context) (map[st
expectedInstances = append(expectedInstances, TigeraStatusLogStorageUsers)
} else {
expectedInstances = append(expectedInstances, TigeraStatusLogStorageESMetrics, TigeraStatusLogStorageKubeController, TigeraStatusLogStorageDashboards)
if r.indexMigration {
// While migrating to single-index storage, the users controller runs in single-tenant mode too.
expectedInstances = append(expectedInstances, TigeraStatusLogStorageUsers)
}
}

// Keep track of which instances are in which state.
Expand Down Expand Up @@ -197,5 +221,12 @@ func updateConditions(currentConditions, desiredConditions map[string]metav1.Con

statusConditions = append(statusConditions, desired)
}

// desiredConditions is a map, so iteration order is random. Sort by type to keep the stored
// conditions stable across reconciles - otherwise every write reorders the list, which counts
// as a change and triggers another reconcile.
sort.Slice(statusConditions, func(i, j int) bool {
return statusConditions[i].Type < statusConditions[j].Type
})
return statusConditions
}
5 changes: 5 additions & 0 deletions pkg/controller/logstorage/kubecontrollers/cloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ func (r *ESKubeControllersController) esGatewayAddCloudModificationsToConfig(c *

// esKubeControllersAddCloudModificationsToConfig modifies the provided *kubecontrollers.KubeControllersConfiguration to include Calico Cloud specific configuration.
func (r *ESKubeControllersController) esKubeControllersAddCloudModificationsToConfig(c *kubecontrollers.KubeControllersConfiguration, reqLogger logr.Logger, ctx context.Context) (reconcile.Result, bool, error) {
// While migrating to single-index storage, es-kube-controllers must not run the elasticsearch
// configuration controller. The operator's log-storage users controller provisions the
// Elasticsearch users instead, so that Linseed gets the RBAC needed for the new indices.
c.IndexMigration = r.indexMigration

if r.cloud && r.elasticExternal && !r.multiTenant {
cloudConfig, err := utils.GetCloudConfig(ctx, r.client)
if err != nil {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ type ESKubeControllersController struct {
elasticExternal bool
multiTenant bool
cloud bool
indexMigration bool
tierWatchReady *utils.ReadyFlag
}

Expand All @@ -88,6 +89,7 @@ func Add(mgr manager.Manager, opts options.ControllerOptions) error {
elasticExternal: opts.ElasticExternal,
multiTenant: opts.MultiTenant,
cloud: opts.Cloud,
indexMigration: opts.IndexMigration,
tierWatchReady: &utils.ReadyFlag{},
}
r.status.Run(opts.ShutdownContext)
Expand Down
11 changes: 8 additions & 3 deletions pkg/controller/logstorage/linseed/linseed_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ type LinseedSubController struct {
multiTenant bool
elasticExternal bool
cloud bool
useSingleIndex bool
}

func Add(mgr manager.Manager, opts options.ControllerOptions) error {
Expand All @@ -87,6 +88,7 @@ func Add(mgr manager.Manager, opts options.ControllerOptions) error {
status: status.New(mgr.GetClient(), "log-storage-access", opts.KubernetesVersion),
elasticExternal: opts.ElasticExternal,
cloud: opts.Cloud,
useSingleIndex: opts.IndexMigration,
}
r.status.Run(opts.ShutdownContext)

Expand Down Expand Up @@ -334,7 +336,7 @@ func (r *LinseedSubController) Reconcile(ctx context.Context, request reconcile.
r.status.SetDegraded(operatorv1.ResourceReadError, "Failed to read cloud config", err, reqLogger)
return reconcile.Result{}, err
}
tenant = cloudConfig.ToTenant()
tenant = cloudConfig.ToTenant(r.useSingleIndex)
}

// Determine the host and port from the URL.
Expand All @@ -361,7 +363,8 @@ func (r *LinseedSubController) Reconcile(ctx context.Context, request reconcile.

// Query the username and password this Linseed instance should use to authenticate with Elasticsearch.
// For multi-tenant systems, credentials are created by the elasticsearch users controller.
// For single-tenant system, these are created by es-kube-controllers.
// For single-tenant systems, these are created by es-kube-controllers, unless the cluster is
// migrating to single-index storage - then the users controller creates them as well.
key = types.NamespacedName{Name: render.ElasticsearchLinseedUserSecret, Namespace: helper.InstallNamespace()}
credentials := corev1.Secret{}
if err = r.client.Get(ctx, key, &credentials); err != nil && !errors.IsNotFound(err) {
Expand Down Expand Up @@ -418,7 +421,8 @@ func (r *LinseedSubController) Reconcile(ctx context.Context, request reconcile.

// Query the username and password this Linseed instance should use to authenticate with Elasticsearch.
// For multi-tenant systems, credentials are created by the elasticsearch users controller.
// For single-tenant system, these are created by es-kube-controllers.
// For single-tenant systems, these are created by es-kube-controllers, unless the cluster is
// migrating to single-index storage - then the users controller creates them as well.
// Delay installing Linseed until available.
// TODO: Switch single-tenant to using operator-provisioned users.
key = types.NamespacedName{Name: render.ElasticsearchLinseedUserSecret, Namespace: helper.InstallNamespace()}
Expand Down Expand Up @@ -468,6 +472,7 @@ func (r *LinseedSubController) Reconcile(ctx context.Context, request reconcile.
ElasticClientCredentialsSecret: &credentials,
LogStorage: logStorage,
Cloud: r.cloud,
UseSingleIndex: r.useSingleIndex,
}
linseedComponent := linseed.Linseed(cfg)

Expand Down
Loading
Loading