Skip to content
125 changes: 81 additions & 44 deletions internal/controller/datadogagent/controller_reconcile_dca.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,66 +30,103 @@ import (
"github.com/DataDog/datadog-operator/internal/controller/datadogagent/override"
"github.com/DataDog/datadog-operator/pkg/condition"
"github.com/DataDog/datadog-operator/pkg/constants"
"github.com/DataDog/datadog-operator/pkg/controller/utils"
"github.com/DataDog/datadog-operator/pkg/controller/utils/datadog"
"github.com/DataDog/datadog-operator/pkg/kubernetes"
)

func (r *Reconciler) reconcileV2ClusterAgent(logger logr.Logger, requiredComponents feature.RequiredComponents, features []feature.Feature, dda *datadoghqv2alpha1.DatadogAgent, resourcesManager feature.ResourceManagers, newStatus *datadoghqv2alpha1.DatadogAgentStatus) (reconcile.Result, error) {
func (r *Reconciler) reconcileV2ClusterAgent(ctx context.Context, logger logr.Logger, requiredComponents feature.RequiredComponents, features []feature.Feature, dda *datadoghqv2alpha1.DatadogAgent, resourcesManager feature.ResourceManagers, newStatus *datadoghqv2alpha1.DatadogAgentStatus) (reconcile.Result, error) {
var result reconcile.Result
now := metav1.NewTime(time.Now())

// Start by creating the Default Cluster-Agent deployment
deployment := componentdca.NewDefaultClusterAgentDeployment(dda.GetObjectMeta(), &dda.Spec)
podManagers := feature.NewPodTemplateManagers(&deployment.Spec.Template)

// Set Global setting on the default deployment
global.ApplyGlobalSettingsClusterAgent(logger, podManagers, dda, resourcesManager, requiredComponents)

// Apply features changes on the Deployment.Spec.Template
var featErrors []error
for _, feat := range features {
if errFeat := feat.ManageClusterAgent(podManagers); errFeat != nil {
featErrors = append(featErrors, errFeat)
// Get provider list for introspection
providerList := map[string]struct{}{kubernetes.LegacyProvider: {}}
if r.options.IntrospectionEnabled {
nodeList, err := r.getNodeList(ctx)
Copy link
Contributor

@celenechang celenechang Jul 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since this is a costly operation, I wonder if it makes sense to make this call up one level and pass it to both this function and reconcileAgentProfiles ?

if err != nil {
return reconcile.Result{}, err
}
providerList = kubernetes.GetProviderListFromNodeList(nodeList, logger)
logger.Info("providerList for cluster agent", "providerList", providerList)
}
if len(featErrors) > 0 {
err := utilerrors.NewAggregate(featErrors)
updateStatusV2WithClusterAgent(deployment, newStatus, now, metav1.ConditionFalse, "ClusterAgent feature error", err.Error())
return result, err
}

deploymentLogger := logger.WithValues("component", datadoghqv2alpha1.ClusterAgentComponentName)

// The requiredComponents can change depending on if updates to features result in disabled components
dcaEnabled := requiredComponents.ClusterAgent.IsEnabled()

// If Override is defined for the clusterAgent component, apply the override on the PodTemplateSpec, it will cascade to container.
if componentOverride, ok := dda.Spec.Override[datadoghqv2alpha1.ClusterAgentComponentName]; ok {
if apiutils.BoolValue(componentOverride.Disabled) {
if dcaEnabled {
// The override supersedes what's set in requiredComponents; update status to reflect the conflict
condition.UpdateDatadogAgentStatusConditions(
newStatus,
metav1.NewTime(time.Now()),
common.OverrideReconcileConflictConditionType,
metav1.ConditionTrue,
"OverrideConflict",
"ClusterAgent component is set to disabled",
true,
)
// Reconcile cluster agent for each provider
var errs []error
for provider := range providerList {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we want to loop over all the providers, because we still want one DCA per cluster. (Let me know if I'm misunderstanding something here.) Perhaps we can add some logic to choose the "best" provider - for instance, if the entire cluster is AKS then use AKS. If it's mixed then make a deterministic choice

Copy link
Contributor

@swang392 swang392 Jul 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made a fix for this in the EKS control plane monitoring PR #2031 (39d8c27)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will be waiting for Sarah to merge her PR into main, and will then update my branch since the issue has been resolved

logger.Info("DCA providerList", "provider", provider)
// Start by creating the Default Cluster-Agent deployment
deployment := componentdca.NewDefaultClusterAgentDeployment(dda.GetObjectMeta(), &dda.Spec)
podManagers := feature.NewPodTemplateManagers(&deployment.Spec.Template)

// Set Global setting on the default deployment
global.ApplyGlobalSettingsClusterAgent(logger, podManagers, dda, resourcesManager, requiredComponents)

// Apply features changes on the Deployment.Spec.Template
var featErrors []error
for _, feat := range features {
logger.Info("DCA feature", "feature", feat.ID())
if errFeat := feat.ManageClusterAgent(podManagers, provider); errFeat != nil {
featErrors = append(featErrors, errFeat)
}
}
if len(featErrors) > 0 {
err := utilerrors.NewAggregate(featErrors)
updateStatusV2WithClusterAgent(deployment, newStatus, now, metav1.ConditionFalse, "ClusterAgent feature error", err.Error())
return result, err
}

deploymentLogger := logger.WithValues("component", datadoghqv2alpha1.ClusterAgentComponentName, "provider", provider)

// The requiredComponents can change depending on if updates to features result in disabled components
dcaEnabled := requiredComponents.ClusterAgent.IsEnabled()

// If Override is defined for the clusterAgent component, apply the override on the PodTemplateSpec, it will cascade to container.
if componentOverride, ok := dda.Spec.Override[datadoghqv2alpha1.ClusterAgentComponentName]; ok {
if apiutils.BoolValue(componentOverride.Disabled) {
if dcaEnabled {
// The override supersedes what's set in requiredComponents; update status to reflect the conflict
condition.UpdateDatadogAgentStatusConditions(
newStatus,
metav1.NewTime(time.Now()),
common.OverrideReconcileConflictConditionType,
metav1.ConditionTrue,
"OverrideConflict",
"ClusterAgent component is set to disabled",
true,
)
}
deleteStatusV2WithClusterAgent(newStatus)
return r.cleanupV2ClusterAgent(deploymentLogger, dda, deployment, resourcesManager, newStatus)
}
override.PodTemplateSpec(logger, podManagers, componentOverride, datadoghqv2alpha1.ClusterAgentComponentName, dda.Name)
override.Deployment(deployment, componentOverride)
} else if !dcaEnabled {
// If the override is not defined, then disable based on dcaEnabled value
deleteStatusV2WithClusterAgent(newStatus)
return r.cleanupV2ClusterAgent(deploymentLogger, dda, deployment, resourcesManager, newStatus)
}
override.PodTemplateSpec(logger, podManagers, componentOverride, datadoghqv2alpha1.ClusterAgentComponentName, dda.Name)
override.Deployment(deployment, componentOverride)
} else if !dcaEnabled {
// If the override is not defined, then disable based on dcaEnabled value
deleteStatusV2WithClusterAgent(newStatus)
return r.cleanupV2ClusterAgent(deploymentLogger, dda, deployment, resourcesManager, newStatus)

// Add provider label to deployment
if deployment.Labels == nil {
deployment.Labels = make(map[string]string)
}
deployment.Labels[constants.MD5AgentDeploymentProviderLabelKey] = provider

res, err := r.createOrUpdateDeployment(deploymentLogger, dda, deployment, newStatus, updateStatusV2WithClusterAgent)
if err != nil {
errs = append(errs, err)
}
if utils.ShouldReturn(res, err) {
return res, err
}
}

return r.createOrUpdateDeployment(deploymentLogger, dda, deployment, newStatus, updateStatusV2WithClusterAgent)
if len(errs) > 0 {
return result, utilerrors.NewAggregate(errs)
}

condition.UpdateDatadogAgentStatusConditions(newStatus, now, common.ClusterAgentReconcileConditionType, metav1.ConditionTrue, "reconcile_succeed", "reconcile succeed", false)
return reconcile.Result{}, nil
}

func updateStatusV2WithClusterAgent(dca *appsv1.Deployment, newStatus *datadoghqv2alpha1.DatadogAgentStatus, updateTime metav1.Time, status metav1.ConditionStatus, reason, message string) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ func (r *Reconciler) reconcileInstanceV2(ctx context.Context, logger logr.Logger
// 2. Reconcile each component.
// 2.a. Cluster Agent

result, err = r.reconcileV2ClusterAgent(logger, requiredComponents, append(configuredFeatures, enabledFeatures...), instance, resourceManagers, newStatus)
result, err = r.reconcileV2ClusterAgent(ctx, logger, requiredComponents, append(configuredFeatures, enabledFeatures...), instance, resourceManagers, newStatus)
if utils.ShouldReturn(result, err) {
return r.updateStatusIfNeededV2(logger, instance, newStatus, result, err, now)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func (df *dummyFeature) ManageDependencies(managers feature.ResourceManagers) er
}

// ManageClusterAgent returns a predefined error (or nil for success).
func (df *dummyFeature) ManageClusterAgent(managers feature.PodTemplateManagers) error {
func (df *dummyFeature) ManageClusterAgent(managers feature.PodTemplateManagers, provider string) error {
return df.ManageClusterAgentError
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,5 @@ const (
DDAdmissionControllerCWSInstrumentationEnabled = "DD_ADMISSION_CONTROLLER_CWS_INSTRUMENTATION_ENABLED"
DDAdmissionControllerCWSInstrumentationMode = "DD_ADMISSION_CONTROLLER_CWS_INSTRUMENTATION_MODE"
DDAdmissionControllerKubernetesAdmissionEventsEnabled = "DD_ADMISSION_CONTROLLER_KUBERNETES_ADMISSION_EVENTS_ENABLED"
DDAdmissionControllerAddAKSSelectors = "DD_ADMISSION_CONTROLLER_ADD_AKS_SELECTORS"
)
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
cilium "github.com/DataDog/datadog-operator/pkg/cilium/v1"
"github.com/DataDog/datadog-operator/pkg/constants"
"github.com/DataDog/datadog-operator/pkg/images"
"github.com/DataDog/datadog-operator/pkg/kubernetes"
)

func init() {
Expand Down Expand Up @@ -323,7 +324,7 @@ func (f *admissionControllerFeature) ManageDependencies(managers feature.Resourc
return nil
}

func (f *admissionControllerFeature) ManageClusterAgent(managers feature.PodTemplateManagers) error {
func (f *admissionControllerFeature) ManageClusterAgent(managers feature.PodTemplateManagers, provider string) error {
managers.EnvVar().AddEnvVarToContainer(apicommon.ClusterAgentContainerName, &corev1.EnvVar{
Name: DDAdmissionControllerEnabled,
Value: "true",
Expand Down Expand Up @@ -405,6 +406,14 @@ func (f *admissionControllerFeature) ManageClusterAgent(managers feature.PodTemp
Value: f.webhookName,
})

_, providerLabel := kubernetes.GetProviderLabelKeyValue(provider)
if providerLabel == kubernetes.AKSRoleType {
managers.EnvVar().AddEnvVarToContainer(apicommon.ClusterAgentContainerName, &corev1.EnvVar{
Name: DDAdmissionControllerAddAKSSelectors,
Value: "true",
})
}

if f.agentSidecarConfig != nil {
managers.EnvVar().AddEnvVarToContainer(apicommon.ClusterAgentContainerName, &corev1.EnvVar{
Name: DDAdmissionControllerAgentSidecarEnabled,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ import (
"github.com/DataDog/datadog-operator/internal/controller/datadogagent/feature/fake"
"github.com/DataDog/datadog-operator/internal/controller/datadogagent/feature/test"
"github.com/DataDog/datadog-operator/pkg/images"
"github.com/DataDog/datadog-operator/pkg/kubernetes"
"github.com/DataDog/datadog-operator/pkg/testutils"

"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
logf "sigs.k8s.io/controller-runtime/pkg/log"
)

func Test_admissionControllerFeature_Configure(t *testing.T) {
Expand Down Expand Up @@ -429,3 +431,31 @@ func sidecarInjectionWantFunc(acm, acRegistry, sidecarRegstry, imageName, imageT
)
}
}

func TestAdmissionController_AddAKSSelectorsEnvVar(t *testing.T) {
logger := logf.Log.WithName("TestAdmissionController_AddAKSSelectorsEnvVar")

dda := testutils.NewDatadogAgentBuilder().
WithAdmissionControllerEnabled(true).
Build()

feat := buildAdmissionControllerFeature(&feature.Options{Logger: logger})
feat.Configure(dda, &dda.Spec, nil)

ptm := fake.NewPodTemplateManagers(t, corev1.PodTemplateSpec{})

aksProvider := kubernetes.AKSCloudProvider + "-" + kubernetes.AKSRoleType

err := feat.ManageClusterAgent(ptm, aksProvider)
assert.NoError(t, err)

envs := ptm.EnvVarMgr.EnvVarsByC[apicommon.ClusterAgentContainerName]
expected := &corev1.EnvVar{Name: DDAdmissionControllerAddAKSSelectors, Value: "true"}
assert.Contains(t, envs, expected, "expected env var %s not found when provider is %s", DDAdmissionControllerAddAKSSelectors, aksProvider)

ptmNoAks := fake.NewPodTemplateManagers(t, corev1.PodTemplateSpec{})
err = feat.ManageClusterAgent(ptmNoAks, kubernetes.DefaultProvider)
assert.NoError(t, err)
envsNoAks := ptmNoAks.EnvVarMgr.EnvVarsByC[apicommon.ClusterAgentContainerName]
assert.NotContains(t, envsNoAks, expected, "env var %s should not be present when provider is not AKS", DDAdmissionControllerAddAKSSelectors)
}
2 changes: 1 addition & 1 deletion internal/controller/datadogagent/feature/apm/feature.go
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ func (f *apmFeature) ManageDependencies(managers feature.ResourceManagers) error

// ManageClusterAgent allows a feature to configure the ClusterAgent's corev1.PodTemplateSpec
// It should do nothing if the feature doesn't need to configure it.
func (f *apmFeature) ManageClusterAgent(managers feature.PodTemplateManagers) error {
func (f *apmFeature) ManageClusterAgent(managers feature.PodTemplateManagers, provider string) error {
if f.singleStepInstrumentation != nil {
if len(f.singleStepInstrumentation.disabledNamespaces) > 0 && len(f.singleStepInstrumentation.enabledNamespaces) > 0 {
// This configuration is not supported
Expand Down
2 changes: 1 addition & 1 deletion internal/controller/datadogagent/feature/asm/feature.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func (f *asmFeature) ManageDependencies(managers feature.ResourceManagers) error

// ManageClusterAgent allows a feature to configure the ClusterAgent's corev1.PodTemplateSpec
// It should do nothing if the feature doesn't need to configure it.
func (f *asmFeature) ManageClusterAgent(managers feature.PodTemplateManagers) error {
func (f *asmFeature) ManageClusterAgent(managers feature.PodTemplateManagers, provider string) error {
if f.threatsEnabled {
if err := managers.EnvVar().AddEnvVarToContainerWithMergeFunc(apicommon.ClusterAgentContainerName, &corev1.EnvVar{
Name: DDAdmissionControllerAppsecEnabled,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ func (f *autoscalingFeature) ManageDependencies(managers feature.ResourceManager

// ManageClusterAgent allows a feature to configure the ClusterAgent's corev1.PodTemplateSpec
// It should do nothing if the feature doesn't need to configure it.
func (f *autoscalingFeature) ManageClusterAgent(managers feature.PodTemplateManagers) error {
func (f *autoscalingFeature) ManageClusterAgent(managers feature.PodTemplateManagers, provider string) error {
managers.EnvVar().AddEnvVarToContainer(apicommon.ClusterAgentContainerName, &corev1.EnvVar{
Name: DDAutoscalingWorkloadEnabled,
Value: "true",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ func (f *clusterChecksFeature) ManageDependencies(managers feature.ResourceManag
return nil
}

func (f *clusterChecksFeature) ManageClusterAgent(managers feature.PodTemplateManagers) error {
func (f *clusterChecksFeature) ManageClusterAgent(managers feature.PodTemplateManagers, provider string) error {
managers.EnvVar().AddEnvVarToContainer(
apicommon.ClusterAgentContainerName,
&corev1.EnvVar{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ func TestClusterAgentChecksumsDifferentForDifferentConfig(t *testing.T) {

for _, datadogAgent := range datadogAgents {
feature.Configure(datadogAgent, &datadogAgent.Spec, nil)
feature.ManageClusterAgent(podTemplateManager)
feature.ManageClusterAgent(podTemplateManager, "")
md5 := podTemplateManager.AnnotationMgr.Annotations[annotationKey]
md5Values[md5] = ""
}
Expand Down
2 changes: 1 addition & 1 deletion internal/controller/datadogagent/feature/cspm/feature.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ func (f *cspmFeature) ManageDependencies(managers feature.ResourceManagers) erro

// ManageClusterAgent allows a feature to configure the ClusterAgent's corev1.PodTemplateSpec
// It should do nothing if the feature doesn't need to configure it.
func (f *cspmFeature) ManageClusterAgent(managers feature.PodTemplateManagers) error {
func (f *cspmFeature) ManageClusterAgent(managers feature.PodTemplateManagers, provider string) error {
if f.customConfig != nil {
var vol corev1.Volume
var volMount corev1.VolumeMount
Expand Down
2 changes: 1 addition & 1 deletion internal/controller/datadogagent/feature/cws/feature.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ func (f *cwsFeature) ManageDependencies(managers feature.ResourceManagers) error

// ManageClusterAgent allows a feature to configure the ClusterAgent's corev1.PodTemplateSpec
// It should do nothing if the feature doesn't need to configure it.
func (f *cwsFeature) ManageClusterAgent(managers feature.PodTemplateManagers) error {
func (f *cwsFeature) ManageClusterAgent(managers feature.PodTemplateManagers, provider string) error {
return nil
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ func (f *dogstatsdFeature) ManageDependencies(managers feature.ResourceManagers)

// ManageClusterAgent allows a feature to configure the ClusterAgent's corev1.PodTemplateSpec
// It should do nothing if the feature doesn't need to configure it.
func (f *dogstatsdFeature) ManageClusterAgent(managers feature.PodTemplateManagers) error {
func (f *dogstatsdFeature) ManageClusterAgent(managers feature.PodTemplateManagers, provider string) error {
return nil
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func (f *dummyFeature) ManageDependencies(managers feature.ResourceManagers) err

// ManageClusterAgent allows a feature to configure the ClusterAgent's corev1.PodTemplateSpec
// It should do nothing if the feature doesn't need to configure it.
func (f *dummyFeature) ManageClusterAgent(managers feature.PodTemplateManagers) error {
func (f *dummyFeature) ManageClusterAgent(managers feature.PodTemplateManagers, provider string) error {
podTemplate := managers.PodTemplateSpec()
if podTemplate.Labels == nil {
podTemplate.Labels = make(map[string]string)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func (f *ebpfCheckFeature) ManageDependencies(managers feature.ResourceManagers)

// ManageClusterAgent allows a feature to configure the ClusterAgent's corev1.PodTemplateSpec
// It should do nothing if the feature doesn't need to configure it.
func (f *ebpfCheckFeature) ManageClusterAgent(managers feature.PodTemplateManagers) error {
func (f *ebpfCheckFeature) ManageClusterAgent(managers feature.PodTemplateManagers, provider string) error {
return nil
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ func (f *defaultFeature) ManageDependencies(managers feature.ResourceManagers) e

// ManageClusterAgent allows a feature to configure the ClusterAgent's corev1.PodTemplateSpec
// It should do nothing if the feature doesn't need to configure it.
func (f *defaultFeature) ManageClusterAgent(managers feature.PodTemplateManagers) error {
func (f *defaultFeature) ManageClusterAgent(managers feature.PodTemplateManagers, provider string) error {
return nil
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ func (f *eventCollectionFeature) ManageDependencies(managers feature.ResourceMan

// ManageClusterAgent allows a feature to configure the ClusterAgent's corev1.PodTemplateSpec
// It should do nothing if the feature doesn't need to configure it.
func (f *eventCollectionFeature) ManageClusterAgent(managers feature.PodTemplateManagers) error {
func (f *eventCollectionFeature) ManageClusterAgent(managers feature.PodTemplateManagers, provider string) error {
// Env vars
managers.EnvVar().AddEnvVarToContainer(apicommon.ClusterAgentContainerName, &corev1.EnvVar{
Name: DDCollectKubernetesEvents,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ func (f *externalMetricsFeature) ManageDependencies(managers feature.ResourceMan

// ManageClusterAgent allows a feature to configure the ClusterAgent's corev1.PodTemplateSpec
// It should do nothing if the feature doesn't need to configure it.
func (f *externalMetricsFeature) ManageClusterAgent(managers feature.PodTemplateManagers) error {
func (f *externalMetricsFeature) ManageClusterAgent(managers feature.PodTemplateManagers, provider string) error {
managers.EnvVar().AddEnvVarToContainer(apicommon.ClusterAgentContainerName, &corev1.EnvVar{
Name: DDExternalMetricsProviderEnabled,
Value: "true",
Expand Down
2 changes: 1 addition & 1 deletion internal/controller/datadogagent/feature/gpu/feature.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ func (f *gpuFeature) ManageDependencies(managers feature.ResourceManagers) error

// ManageClusterAgent allows a feature to configure the ClusterAgent's corev1.PodTemplateSpec
// It should do nothing if the feature doesn't need to configure it.
func (f *gpuFeature) ManageClusterAgent(feature.PodTemplateManagers) error {
func (f *gpuFeature) ManageClusterAgent(managers feature.PodTemplateManagers, provider string) error {
return nil
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ func (f *helmCheckFeature) ManageDependencies(managers feature.ResourceManagers)

// ManageClusterAgent allows a feature to configure the ClusterAgent's corev1.PodTemplateSpec
// It should do nothing if the feature doesn't need to configure it.
func (f *helmCheckFeature) ManageClusterAgent(managers feature.PodTemplateManagers) error {
func (f *helmCheckFeature) ManageClusterAgent(managers feature.PodTemplateManagers, provider string) error {
// Manage Helm check config in configMap
var vol corev1.Volume
var volMount corev1.VolumeMount
Expand Down
Loading
Loading