diff --git a/deploy/resources/addondeploymentconfig.yaml b/deploy/resources/addondeploymentconfig.yaml index 90b1a35..2398994 100644 --- a/deploy/resources/addondeploymentconfig.yaml +++ b/deploy/resources/addondeploymentconfig.yaml @@ -5,5 +5,16 @@ metadata: namespace: open-cluster-management spec: customizedVariables: - - name: loggingSubscriptionChannel - value: stable-5.9 \ No newline at end of file + # Operator Subscription Channels + - name: openshiftLoggingChannel + value: stable-5.9 + # Platform Observability + - name: platformLogsCollection + value: clusterlogforwarders.v1.logging.openshift.io + # User Workloads Observability + - name: userWorkloadLogsCollection + value: clusterlogforwarders.v1.logging.openshift.io + - name: userWorkloadTracesCollection + value: opentelemetrycollectors.v1beta1.opentelemetry.io + - name: userWorkloadInstrumentation + value: instrumentations.v1alpha1.opentelemetry.io diff --git a/internal/addon/helm/values.go b/internal/addon/helm/values.go index fbf3f73..00af279 100644 --- a/internal/addon/helm/values.go +++ b/internal/addon/helm/values.go @@ -2,14 +2,12 @@ package helm import ( "context" - "strconv" "github.com/rhobs/multicluster-observability-addon/internal/addon" lhandlers "github.com/rhobs/multicluster-observability-addon/internal/logging/handlers" lmanifests "github.com/rhobs/multicluster-observability-addon/internal/logging/manifests" thandlers "github.com/rhobs/multicluster-observability-addon/internal/tracing/handlers" tmanifests "github.com/rhobs/multicluster-observability-addon/internal/tracing/manifests" - "k8s.io/klog/v2" "open-cluster-management.io/addon-framework/pkg/addonfactory" addonutils "open-cluster-management.io/addon-framework/pkg/utils" addonapiv1alpha1 "open-cluster-management.io/api/addon/v1alpha1" @@ -25,36 +23,35 @@ type HelmChartValues struct { Tracing tmanifests.TracingValues `json:"tracing"` } -type Options struct { - LoggingDisabled bool - TracingDisabled bool -} - func GetValuesFunc(ctx context.Context, k8s client.Client) addonfactory.GetValuesFunc { return func( cluster *clusterv1.ManagedCluster, - addon *addonapiv1alpha1.ManagedClusterAddOn, + mcAddon *addonapiv1alpha1.ManagedClusterAddOn, ) (addonfactory.Values, error) { // if hub cluster, then don't install anything if isHubCluster(cluster) { return addonfactory.JsonStructToValues(HelmChartValues{}) } - aodc, err := getAddOnDeploymentConfig(ctx, k8s, addon) + aodc, err := getAddOnDeploymentConfig(ctx, k8s, mcAddon) if err != nil { return nil, err } - opts, err := buildOptions(aodc) + opts, err := addon.BuildOptions(aodc) if err != nil { return nil, err } + if !opts.Platform.Enabled && !opts.UserWorkloads.Enabled { + return addonfactory.JsonStructToValues(HelmChartValues{}) + } + userValues := HelmChartValues{ Enabled: true, } - if !opts.LoggingDisabled { - loggingOpts, err := lhandlers.BuildOptions(ctx, k8s, addon, aodc) + if opts.Platform.Logs.CollectionEnabled || opts.UserWorkloads.Logs.CollectionEnabled { + loggingOpts, err := lhandlers.BuildOptions(ctx, k8s, mcAddon, opts.Platform.Logs, opts.Platform.Logs) if err != nil { return nil, err } @@ -66,9 +63,8 @@ func GetValuesFunc(ctx context.Context, k8s client.Client) addonfactory.GetValue userValues.Logging = *logging } - if !opts.TracingDisabled { - klog.Info("Tracing enabled") - tracingOpts, err := thandlers.BuildOptions(ctx, k8s, addon, aodc) + if opts.UserWorkloads.Traces.CollectionEnabled { + tracingOpts, err := thandlers.BuildOptions(ctx, k8s, mcAddon, opts.UserWorkloads.Traces) if err != nil { return nil, err } @@ -94,35 +90,6 @@ func getAddOnDeploymentConfig(ctx context.Context, k8s client.Client, mcAddon *a return addOnDeployment, nil } -func buildOptions(addOnDeployment *addonapiv1alpha1.AddOnDeploymentConfig) (Options, error) { - var opts Options - if addOnDeployment == nil { - return opts, nil - } - - if addOnDeployment.Spec.CustomizedVariables == nil { - return opts, nil - } - - for _, keyvalue := range addOnDeployment.Spec.CustomizedVariables { - switch keyvalue.Name { - case addon.AdcLoggingDisabledKey: - value, err := strconv.ParseBool(keyvalue.Value) - if err != nil { - return opts, err - } - opts.LoggingDisabled = value - case addon.AdcTracingisabledKey: - value, err := strconv.ParseBool(keyvalue.Value) - if err != nil { - return opts, err - } - opts.TracingDisabled = value - } - } - return opts, nil -} - func isHubCluster(cluster *clusterv1.ManagedCluster) bool { val, ok := cluster.Labels[annotationLocalCluster] if !ok { diff --git a/internal/addon/helm/values_test.go b/internal/addon/helm/values_test.go index 7cc898c..f198d0c 100644 --- a/internal/addon/helm/values_test.go +++ b/internal/addon/helm/values_test.go @@ -59,16 +59,7 @@ func Test_Mcoa_Disable_Charts(t *testing.T) { Namespace: "open-cluster-management", }, Spec: addonapiv1alpha1.AddOnDeploymentConfigSpec{ - CustomizedVariables: []addonapiv1alpha1.CustomizedVariable{ - { - Name: "loggingDisabled", - Value: "true", - }, - { - Name: "tracingDisabled", - Value: "true", - }, - }, + CustomizedVariables: []addonapiv1alpha1.CustomizedVariable{}, }, } @@ -77,7 +68,7 @@ func Test_Mcoa_Disable_Charts(t *testing.T) { WithObjects(addOnDeploymentConfig). Build() - loggingAgentAddon, err := addonfactory.NewAgentAddonFactory(addon.Name, addon.FS, addon.McoaChartDir). + agentAddon, err := addonfactory.NewAgentAddonFactory(addon.Name, addon.FS, addon.McoaChartDir). WithGetValuesFuncs(GetValuesFunc(context.TODO(), fakeKubeClient)). WithAgentRegistrationOption(&agent.RegistrationOption{}). WithScheme(scheme.Scheme). @@ -86,9 +77,9 @@ func Test_Mcoa_Disable_Charts(t *testing.T) { klog.Fatalf("failed to build agent %v", err) } - objects, err := loggingAgentAddon.Manifests(managedCluster, managedClusterAddOn) + objects, err := agentAddon.Manifests(managedCluster, managedClusterAddOn) require.NoError(t, err) - require.Equal(t, 2, len(objects)) + require.Empty(t, objects) } func Test_Mcoa_Disable_Chart_Hub(t *testing.T) { diff --git a/internal/addon/options.go b/internal/addon/options.go new file mode 100644 index 0000000..26d04a1 --- /dev/null +++ b/internal/addon/options.go @@ -0,0 +1,101 @@ +package addon + +import ( + addonapiv1alpha1 "open-cluster-management.io/api/addon/v1alpha1" +) + +const ( + // Operator Subscription Channels + KeyOpenShiftLoggingChannel = "openshiftLoggingChannel" + + // Platform Observability Keys + KeyPlatformLogsCollection = "platformLogsCollection" + + // User Workloads Observability Keys + KeyUserWorkloadLogsCollection = "userWorkloadLogsCollection" + KeyUserWorkloadTracesCollection = "userWorkloadTracesCollection" + KeyUserWorkloadTracesInstrumentation = "userWorkloadTracesInstrumentation" +) + +type CollectionKind string + +const ( + ClusterLogForwarderV1 CollectionKind = "clusterlogforwarders.v1.logging.openshift.io" + OpenTelemetryCollectorV1beta1 CollectionKind = "opentelemetrycollectors.v1beta1.opentelemetry.io" +) + +type InstrumentationKind string + +const ( + InstrumentationV1alpha1 InstrumentationKind = "instrumentations.v1alpha1.opentelemetry.io" +) + +type LogsOptions struct { + CollectionEnabled bool + SubscriptionChannel string +} + +type TracesOptions struct { + CollectionEnabled bool + InstrumentationEnabled bool + SubscriptionChannel string +} + +type PlatformOptions struct { + Enabled bool + Logs LogsOptions +} + +type UserWorkloadOptions struct { + Enabled bool + Logs LogsOptions + Traces TracesOptions +} + +type Options struct { + Platform PlatformOptions + UserWorkloads UserWorkloadOptions +} + +func BuildOptions(addOnDeployment *addonapiv1alpha1.AddOnDeploymentConfig) (Options, error) { + var opts Options + if addOnDeployment == nil { + return opts, nil + } + + if addOnDeployment.Spec.CustomizedVariables == nil { + return opts, nil + } + + for _, keyvalue := range addOnDeployment.Spec.CustomizedVariables { + switch keyvalue.Name { + // Operator Subscriptions + case KeyOpenShiftLoggingChannel: + opts.Platform.Logs.SubscriptionChannel = keyvalue.Value + opts.UserWorkloads.Logs.SubscriptionChannel = keyvalue.Value + // Platform Observability Options + case KeyPlatformLogsCollection: + if keyvalue.Value == string(ClusterLogForwarderV1) { + opts.Platform.Enabled = true + opts.Platform.Logs.CollectionEnabled = true + } + // User Workload Observability Options + case KeyUserWorkloadLogsCollection: + if keyvalue.Value == string(ClusterLogForwarderV1) { + opts.UserWorkloads.Enabled = true + opts.UserWorkloads.Logs.CollectionEnabled = true + } + case KeyUserWorkloadTracesCollection: + if keyvalue.Value == string(OpenTelemetryCollectorV1beta1) { + opts.UserWorkloads.Enabled = true + opts.UserWorkloads.Traces.CollectionEnabled = true + } + case KeyUserWorkloadTracesInstrumentation: + if keyvalue.Value == string(InstrumentationV1alpha1) { + opts.UserWorkloads.Enabled = true + opts.UserWorkloads.Traces.InstrumentationEnabled = true + } + } + } + return opts, nil +} diff --git a/internal/addon/var.go b/internal/addon/var.go index 01f9199..c59b635 100644 --- a/internal/addon/var.go +++ b/internal/addon/var.go @@ -13,15 +13,11 @@ const ( TracingChartDir = "manifests/charts/mcoa/charts/tracing" AddonDeploymentConfigResource = "addondeploymentconfigs" - - AdcLoggingDisabledKey = "loggingDisabled" - AdcTracingisabledKey = "tracingDisabled" - - ClusterLogForwardersResource = "clusterlogforwarders" - SpokeCLFName = "mcoa-instance" - SpokeCLFNamespace = "openshift-logging" - clfProbeKey = "isReady" - clfProbePath = ".status.conditions[?(@.type==\"Ready\")].status" + ClusterLogForwardersResource = "clusterlogforwarders" + SpokeCLFName = "mcoa-instance" + SpokeCLFNamespace = "openshift-logging" + clfProbeKey = "isReady" + clfProbePath = ".status.conditions[?(@.type==\"Ready\")].status" OpenTelemetryCollectorsResource = "opentelemetrycollectors" SpokeOTELColName = "mcoa-instance" diff --git a/internal/logging/handlers/handler.go b/internal/logging/handlers/handler.go index c7382ee..a26c6a9 100644 --- a/internal/logging/handlers/handler.go +++ b/internal/logging/handlers/handler.go @@ -11,17 +11,24 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" ) -func BuildOptions(ctx context.Context, k8s client.Client, mcAddon *addonapiv1alpha1.ManagedClusterAddOn, adoc *addonapiv1alpha1.AddOnDeploymentConfig) (manifests.Options, error) { - resources := manifests.Options{ - AddOnDeploymentConfig: adoc, +func BuildOptions(ctx context.Context, k8s client.Client, mcAddon *addonapiv1alpha1.ManagedClusterAddOn, platform, userWorkloads addon.LogsOptions) (manifests.Options, error) { + opts := manifests.Options{ + Platform: platform, + UserWorkloads: userWorkloads, + } + + if platform.SubscriptionChannel != "" { + opts.SubscriptionChannel = platform.SubscriptionChannel + } else { + opts.SubscriptionChannel = userWorkloads.SubscriptionChannel } key := addon.GetObjectKey(mcAddon.Status.ConfigReferences, loggingv1.GroupVersion.Group, addon.ClusterLogForwardersResource) clf := &loggingv1.ClusterLogForwarder{} if err := k8s.Get(ctx, key, clf, &client.GetOptions{}); err != nil { - return resources, err + return opts, err } - resources.ClusterLogForwarder = clf + opts.ClusterLogForwarder = clf targetSecretName := make(map[authentication.Target]string) for _, output := range clf.Spec.Outputs { @@ -31,13 +38,13 @@ func BuildOptions(ctx context.Context, k8s client.Client, mcAddon *addonapiv1alp secretsProvider := authentication.NewSecretsProvider(k8s, clf.Namespace, mcAddon.Namespace) targetSecrets, err := secretsProvider.GenerateSecrets(ctx, clf.Annotations, targetSecretName) if err != nil { - return resources, err + return opts, err } - resources.Secrets, err = secretsProvider.FetchSecrets(ctx, targetSecrets) + opts.Secrets, err = secretsProvider.FetchSecrets(ctx, targetSecrets) if err != nil { - return resources, err + return opts, err } - return resources, nil + return opts, nil } diff --git a/internal/logging/helm_test.go b/internal/logging/helm_test.go index d200164..20fe1d4 100644 --- a/internal/logging/helm_test.go +++ b/internal/logging/helm_test.go @@ -36,10 +36,10 @@ var ( func fakeGetValues(k8s client.Client) addonfactory.GetValuesFunc { return func( - cluster *clusterv1.ManagedCluster, - addon *addonapiv1alpha1.ManagedClusterAddOn, + _ *clusterv1.ManagedCluster, + mcAddon *addonapiv1alpha1.ManagedClusterAddOn, ) (addonfactory.Values, error) { - opts, err := handlers.BuildOptions(context.TODO(), k8s, addon, nil) + opts, err := handlers.BuildOptions(context.TODO(), k8s, mcAddon, addon.LogsOptions{}, addon.LogsOptions{}) if err != nil { return nil, err } diff --git a/internal/logging/manifests/logging.go b/internal/logging/manifests/logging.go index c7b70bf..71d3037 100644 --- a/internal/logging/manifests/logging.go +++ b/internal/logging/manifests/logging.go @@ -2,6 +2,7 @@ package manifests import ( "encoding/json" + "errors" "slices" loggingv1 "github.com/openshift/cluster-logging-operator/apis/logging/v1" @@ -9,16 +10,14 @@ import ( corev1 "k8s.io/api/core/v1" ) -func buildSubscriptionChannel(resources Options) string { - adoc := resources.AddOnDeploymentConfig - if adoc == nil || len(adoc.Spec.CustomizedVariables) == 0 { - return defaultLoggingVersion - } +var ( + errPlatformLogsNotDefined = errors.New("Platform logs not defined") + errUserWorkloadLogsNotDefined = errors.New("User Workloads logs not defined") +) - for _, keyvalue := range adoc.Spec.CustomizedVariables { - if keyvalue.Name == subscriptionChannelValueKey { - return keyvalue.Value - } +func buildSubscriptionChannel(resources Options) string { + if resources.SubscriptionChannel != "" { + return resources.SubscriptionChannel } return defaultLoggingVersion } @@ -47,15 +46,44 @@ func buildSecrets(resources Options) ([]SecretValue, error) { return secretsValue, nil } -func buildClusterLogForwarderSpec(resources Options) (*loggingv1.ClusterLogForwarderSpec, error) { - clf := resources.ClusterLogForwarder - for target, secret := range resources.Secrets { +func buildClusterLogForwarderSpec(opts Options) (*loggingv1.ClusterLogForwarderSpec, error) { + clf := opts.ClusterLogForwarder + for target, secret := range opts.Secrets { if err := templateWithSecret(&clf.Spec, target, secret); err != nil { return nil, err } } clf.Spec.ServiceAccountName = "mcoa-logcollector" + // Validate Platform Logs enabled + var ( + platformDetected bool + userWorkloadsDetected bool + ) + for _, pipeline := range clf.Spec.Pipelines { + // Consider pipelines without outputs invalid + if pipeline.OutputRefs == nil { + continue + } + + for _, ref := range pipeline.InputRefs { + if ref == loggingv1.InputNameInfrastructure || ref == loggingv1.InputNameAudit { + platformDetected = true + } + if ref == loggingv1.InputNameApplication { + userWorkloadsDetected = true + } + } + } + + if opts.Platform.CollectionEnabled && !platformDetected { + return nil, errPlatformLogsNotDefined + } + + if opts.UserWorkloads.CollectionEnabled && !userWorkloadsDetected { + return nil, errUserWorkloadLogsNotDefined + } + return &clf.Spec, nil } diff --git a/internal/logging/manifests/logging_test.go b/internal/logging/manifests/logging_test.go index 8b8cd58..a34ab22 100644 --- a/internal/logging/manifests/logging_test.go +++ b/internal/logging/manifests/logging_test.go @@ -24,31 +24,19 @@ func Test_BuildSubscriptionChannel(t *testing.T) { subChannel string }{ { - name: "unknown key", - key: "test", - value: "stable-1.0", + name: "not set", subChannel: "stable-5.9", }, { - name: "known key", - key: "loggingSubscriptionChannel", + name: "user set", value: "stable-5.7", subChannel: "stable-5.7", }, } { + tc := tc t.Run(tc.name, func(t *testing.T) { - adoc := &addonapiv1alpha1.AddOnDeploymentConfig{ - Spec: addonapiv1alpha1.AddOnDeploymentConfigSpec{ - CustomizedVariables: []addonapiv1alpha1.CustomizedVariable{ - { - Name: tc.key, - Value: tc.value, - }, - }, - }, - } resources := Options{ - AddOnDeploymentConfig: adoc, + SubscriptionChannel: tc.value, } subChannel := buildSubscriptionChannel(resources) require.Equal(t, tc.subChannel, subChannel) diff --git a/internal/logging/manifests/options.go b/internal/logging/manifests/options.go index d84e03e..088d5f2 100644 --- a/internal/logging/manifests/options.go +++ b/internal/logging/manifests/options.go @@ -2,13 +2,15 @@ package manifests import ( loggingv1 "github.com/openshift/cluster-logging-operator/apis/logging/v1" + "github.com/rhobs/multicluster-observability-addon/internal/addon" "github.com/rhobs/multicluster-observability-addon/internal/addon/authentication" corev1 "k8s.io/api/core/v1" - addonapiv1alpha1 "open-cluster-management.io/api/addon/v1alpha1" ) type Options struct { - Secrets map[authentication.Target]corev1.Secret - ClusterLogForwarder *loggingv1.ClusterLogForwarder - AddOnDeploymentConfig *addonapiv1alpha1.AddOnDeploymentConfig + Secrets map[authentication.Target]corev1.Secret + ClusterLogForwarder *loggingv1.ClusterLogForwarder + Platform addon.LogsOptions + UserWorkloads addon.LogsOptions + SubscriptionChannel string } diff --git a/internal/tracing/handlers/handler.go b/internal/tracing/handlers/handler.go index a829b80..4d2a283 100644 --- a/internal/tracing/handlers/handler.go +++ b/internal/tracing/handlers/handler.go @@ -21,38 +21,38 @@ var ( errNoVolumeMountForSecret = fmt.Errorf("no volumemount found for secret") ) -func BuildOptions(ctx context.Context, k8s client.Client, mcAddon *addonapiv1alpha1.ManagedClusterAddOn, adoc *addonapiv1alpha1.AddOnDeploymentConfig) (manifests.Options, error) { - resources := manifests.Options{ - AddOnDeploymentConfig: adoc, - ClusterName: mcAddon.Namespace, +func BuildOptions(ctx context.Context, k8s client.Client, mcAddon *addonapiv1alpha1.ManagedClusterAddOn, userWorkloads addon.TracesOptions) (manifests.Options, error) { + opts := manifests.Options{ + ClusterName: mcAddon.Namespace, + UserWorkloads: userWorkloads, } klog.Info("Retrieving OpenTelemetry Collector template") key := addon.GetObjectKey(mcAddon.Status.ConfigReferences, otelv1beta1.GroupVersion.Group, addon.OpenTelemetryCollectorsResource) otelCol := &otelv1beta1.OpenTelemetryCollector{} if err := k8s.Get(ctx, key, otelCol, &client.GetOptions{}); err != nil { - return resources, err + return opts, err } - resources.OpenTelemetryCollector = otelCol + opts.OpenTelemetryCollector = otelCol klog.Info("OpenTelemetry Collector template found") targetSecretName, err := buildExportersSecrets(otelCol) if err != nil { - return resources, nil + return opts, nil } secretsProvider := authentication.NewSecretsProvider(k8s, otelCol.Namespace, mcAddon.Namespace) targetsSecret, err := secretsProvider.GenerateSecrets(ctx, otelCol.Annotations, targetSecretName) if err != nil { - return resources, err + return opts, err } - resources.Secrets, err = secretsProvider.FetchSecrets(ctx, targetsSecret) + opts.Secrets, err = secretsProvider.FetchSecrets(ctx, targetsSecret) if err != nil { - return resources, err + return opts, err } - return resources, nil + return opts, nil } func buildExportersSecrets(otelCol *otelv1beta1.OpenTelemetryCollector) (map[authentication.Target]string, error) { diff --git a/internal/tracing/helm_test.go b/internal/tracing/helm_test.go index 67d734c..df09fee 100644 --- a/internal/tracing/helm_test.go +++ b/internal/tracing/helm_test.go @@ -34,9 +34,9 @@ var ( func fakeGetValues(k8s client.Client) addonfactory.GetValuesFunc { return func( cluster *clusterv1.ManagedCluster, - addon *addonapiv1alpha1.ManagedClusterAddOn, + mcAddon *addonapiv1alpha1.ManagedClusterAddOn, ) (addonfactory.Values, error) { - opts, err := handlers.BuildOptions(context.TODO(), k8s, addon, nil) + opts, err := handlers.BuildOptions(context.TODO(), k8s, mcAddon, addon.TracesOptions{}) if err != nil { return nil, err } diff --git a/internal/tracing/manifests/options.go b/internal/tracing/manifests/options.go index 5482474..319db2f 100644 --- a/internal/tracing/manifests/options.go +++ b/internal/tracing/manifests/options.go @@ -2,6 +2,7 @@ package manifests import ( otelv1beta1 "github.com/open-telemetry/opentelemetry-operator/apis/v1beta1" + "github.com/rhobs/multicluster-observability-addon/internal/addon" "github.com/rhobs/multicluster-observability-addon/internal/addon/authentication" corev1 "k8s.io/api/core/v1" addonapiv1alpha1 "open-cluster-management.io/api/addon/v1alpha1" @@ -13,4 +14,5 @@ type Options struct { ConfigMaps []corev1.ConfigMap OpenTelemetryCollector *otelv1beta1.OpenTelemetryCollector AddOnDeploymentConfig *addonapiv1alpha1.AddOnDeploymentConfig + UserWorkloads addon.TracesOptions }