diff --git a/apis/datadoghq/v2alpha1/condition.go b/apis/datadoghq/v2alpha1/condition.go index b1a95c774..947e84b7d 100644 --- a/apis/datadoghq/v2alpha1/condition.go +++ b/apis/datadoghq/v2alpha1/condition.go @@ -8,6 +8,8 @@ import ( appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + edsdatadoghqv1alpha1 "github.com/DataDog/extendeddaemonset/api/v1alpha1" ) // DatadogAgentState type representing the deployment state of the different Agent components. @@ -181,3 +183,38 @@ func UpdateDaemonSetStatus(ds *appsv1.DaemonSet, dsStatus *DaemonSetStatus, upda dsStatus.DaemonsetName = ds.ObjectMeta.Name return dsStatus } + +// UpdateExtendedDaemonSetStatus updates an ExtendedDaemonSet's DaemonSetStatus +func UpdateExtendedDaemonSetStatus(eds *edsdatadoghqv1alpha1.ExtendedDaemonSet, dsStatus *DaemonSetStatus, updateTime *metav1.Time) *DaemonSetStatus { + if dsStatus == nil { + dsStatus = &DaemonSetStatus{} + } + if updateTime != nil { + dsStatus.LastUpdate = updateTime + } + if hash, ok := eds.Annotations[apicommon.MD5AgentDeploymentAnnotationKey]; ok { + dsStatus.CurrentHash = hash + } + dsStatus.Desired = eds.Status.Desired + dsStatus.Current = eds.Status.Current + dsStatus.Ready = eds.Status.Ready + dsStatus.Available = eds.Status.Available + dsStatus.UpToDate = eds.Status.UpToDate + + var deploymentState DatadogAgentState + switch { + case eds.Status.Canary != nil: + deploymentState = DatadogAgentStateCanary + case dsStatus.UpToDate != dsStatus.Desired: + deploymentState = DatadogAgentStateUpdating + case dsStatus.Ready == 0: + deploymentState = DatadogAgentStateProgressing + default: + deploymentState = DatadogAgentStateRunning + } + + dsStatus.State = fmt.Sprintf("%v", deploymentState) + dsStatus.Status = fmt.Sprintf("%v (%d/%d/%d)", deploymentState, dsStatus.Desired, dsStatus.Ready, dsStatus.UpToDate) + dsStatus.DaemonsetName = eds.ObjectMeta.Name + return dsStatus +} diff --git a/controllers/datadogagent/component/new.go b/controllers/datadogagent/component/new.go index 8edff58dc..a67244113 100644 --- a/controllers/datadogagent/component/new.go +++ b/controllers/datadogagent/component/new.go @@ -75,8 +75,11 @@ func NewExtendedDaemonset(owner metav1.Object, componentKind, componentName, ver func defaultEDSStrategy() edsv1alpha1.ExtendedDaemonSetSpecStrategy { return edsv1alpha1.ExtendedDaemonSetSpecStrategy{ - Canary: edsv1alpha1.DefaultExtendedDaemonSetSpecStrategyCanary(nil, edsv1alpha1.ExtendedDaemonSetSpecStrategyCanaryValidationModeAuto), - RollingUpdate: *edsv1alpha1.DefaultExtendedDaemonSetSpecStrategyRollingUpdate(nil), + Canary: edsv1alpha1.DefaultExtendedDaemonSetSpecStrategyCanary( + &edsv1alpha1.ExtendedDaemonSetSpecStrategyCanary{}, + edsv1alpha1.ExtendedDaemonSetSpecStrategyCanaryValidationModeAuto, + ), + RollingUpdate: *edsv1alpha1.DefaultExtendedDaemonSetSpecStrategyRollingUpdate(&edsv1alpha1.ExtendedDaemonSetSpecStrategyRollingUpdate{}), ReconcileFrequency: &metav1.Duration{ Duration: apicommon.DefaultReconcileFrequency, }, diff --git a/controllers/datadogagent/controller_reconcile_agent.go b/controllers/datadogagent/controller_reconcile_agent.go index 0bb1b3a22..576581f0e 100644 --- a/controllers/datadogagent/controller_reconcile_agent.go +++ b/controllers/datadogagent/controller_reconcile_agent.go @@ -15,6 +15,7 @@ import ( appsv1 "k8s.io/api/apps/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + edsv1alpha1 "github.com/DataDog/extendeddaemonset/api/v1alpha1" "github.com/go-logr/logr" "sigs.k8s.io/controller-runtime/pkg/reconcile" ) @@ -22,15 +23,25 @@ import ( func (r *Reconciler) reconcileV2Agent(logger logr.Logger, features []feature.Feature, dda *datadoghqv2alpha1.DatadogAgent, resourcesManager feature.ResourceManagers, newStatus *datadoghqv2alpha1.DatadogAgentStatus, requiredContainers []common.AgentContainerName) (reconcile.Result, error) { var result reconcile.Result var err error + var eds *edsv1alpha1.ExtendedDaemonSet + var daemonset *appsv1.DaemonSet + var podManagers feature.PodTemplateManagers - // TODO for now only support Daemonset (not EDS) + if r.options.SupportExtendedDaemonset { + // Start by creating the Default Agent extendeddaemonset + eds = componentagent.NewDefaultAgentExtendedDaemonset(dda, requiredContainers) + podManagers = feature.NewPodTemplateManagers(&eds.Spec.Template) - // Start by creating the Default Cluster-Agent deployment - daemonset := componentagent.NewDefaultAgentDaemonset(dda, requiredContainers) - podManagers := feature.NewPodTemplateManagers(&daemonset.Spec.Template) + // Set Global setting on the default extendeddaemonset + eds.Spec.Template = *override.ApplyGlobalSettings(logger, podManagers, dda, resourcesManager, datadoghqv2alpha1.NodeAgentComponentName) + } else { + // Start by creating the Default Agent daemonset + daemonset = componentagent.NewDefaultAgentDaemonset(dda, requiredContainers) + podManagers = feature.NewPodTemplateManagers(&daemonset.Spec.Template) - // Set Global setting on the default deployment - daemonset.Spec.Template = *override.ApplyGlobalSettings(logger, podManagers, dda, resourcesManager, datadoghqv2alpha1.NodeAgentComponentName) + // Set Global setting on the default daemonset + daemonset.Spec.Template = *override.ApplyGlobalSettings(logger, podManagers, dda, resourcesManager, datadoghqv2alpha1.NodeAgentComponentName) + } // Apply features changes on the Deployment.Spec.Template for _, feat := range features { @@ -39,7 +50,7 @@ func (r *Reconciler) reconcileV2Agent(logger logr.Logger, features []feature.Fea } } - // If Override is define for the cluster-checks-runner component, apply the override on the PodTemplateSpec, it will cascade to container. + // If Override is defined for the node agent component, apply the override on the PodTemplateSpec, it will cascade to container. if _, ok := dda.Spec.Override[datadoghqv2alpha1.NodeAgentComponentName]; ok { _, err = override.PodTemplateSpec(podManagers, dda.Spec.Override[datadoghqv2alpha1.NodeAgentComponentName]) if err != nil { @@ -48,10 +59,18 @@ func (r *Reconciler) reconcileV2Agent(logger logr.Logger, features []feature.Fea } daemonsetLogger := logger.WithValues("component", datadoghqv2alpha1.NodeAgentComponentName) - return r.createOrUpdateDaemonset(daemonsetLogger, dda, daemonset, newStatus, updateStatusV2WithAgent) + if r.options.SupportExtendedDaemonset { + return r.createOrUpdateExtendedDaemonset(daemonsetLogger, dda, eds, newStatus, updateEDSStatusV2WithAgent) + } + return r.createOrUpdateDaemonset(daemonsetLogger, dda, daemonset, newStatus, updateDSStatusV2WithAgent) } -func updateStatusV2WithAgent(dda *appsv1.DaemonSet, newStatus *datadoghqv2alpha1.DatadogAgentStatus, updateTime metav1.Time, status metav1.ConditionStatus, reason, message string) { +func updateDSStatusV2WithAgent(dda *appsv1.DaemonSet, newStatus *datadoghqv2alpha1.DatadogAgentStatus, updateTime metav1.Time, status metav1.ConditionStatus, reason, message string) { newStatus.Agent = datadoghqv2alpha1.UpdateDaemonSetStatus(dda, newStatus.Agent, &updateTime) datadoghqv2alpha1.UpdateDatadogAgentStatusConditions(newStatus, updateTime, datadoghqv2alpha1.AgentReconcileConditionType, status, reason, message, true) } + +func updateEDSStatusV2WithAgent(eds *edsv1alpha1.ExtendedDaemonSet, newStatus *datadoghqv2alpha1.DatadogAgentStatus, updateTime metav1.Time, status metav1.ConditionStatus, reason, message string) { + newStatus.Agent = datadoghqv2alpha1.UpdateExtendedDaemonSetStatus(eds, newStatus.Agent, &updateTime) + datadoghqv2alpha1.UpdateDatadogAgentStatusConditions(newStatus, updateTime, datadoghqv2alpha1.AgentReconcileConditionType, status, reason, message, true) +} diff --git a/controllers/datadogagent/controller_reconcile_v2_common.go b/controllers/datadogagent/controller_reconcile_v2_common.go index 2851ed4e1..72a61d9b9 100644 --- a/controllers/datadogagent/controller_reconcile_v2_common.go +++ b/controllers/datadogagent/controller_reconcile_v2_common.go @@ -21,10 +21,13 @@ import ( "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/reconcile" + + edsv1alpha1 "github.com/DataDog/extendeddaemonset/api/v1alpha1" ) type updateDepStatusComponentFunc func(deployment *appsv1.Deployment, newStatus *datadoghqv2alpha1.DatadogAgentStatus, updateTime metav1.Time, status metav1.ConditionStatus, reason, message string) type updateDSStatusComponentFunc func(daemonset *appsv1.DaemonSet, newStatus *datadoghqv2alpha1.DatadogAgentStatus, updateTime metav1.Time, status metav1.ConditionStatus, reason, message string) +type updateEDSStatusComponentFunc func(eds *edsv1alpha1.ExtendedDaemonSet, newStatus *datadoghqv2alpha1.DatadogAgentStatus, updateTime metav1.Time, status metav1.ConditionStatus, reason, message string) func (r *Reconciler) createOrUpdateDeployment(parentLogger logr.Logger, dda *datadoghqv2alpha1.DatadogAgent, deployment *appsv1.Deployment, newStatus *datadoghqv2alpha1.DatadogAgentStatus, updateStatusFunc updateDepStatusComponentFunc) (reconcile.Result, error) { logger := parentLogger.WithValues("deployment.Namespace", deployment.Namespace, "deployment.Name", deployment.Name) @@ -32,12 +35,12 @@ func (r *Reconciler) createOrUpdateDeployment(parentLogger logr.Logger, dda *dat var result reconcile.Result var err error - // Set DatadogAgent instance instance as the owner and controller + // Set DatadogAgent instance as the owner and controller if err = controllerutil.SetControllerReference(dda, deployment, r.scheme); err != nil { return reconcile.Result{}, err } - // From here the PodTemplateSpec should be ready, we can generate the hash that will be use to compare this deployment with the current (if exist). + // From here the PodTemplateSpec should be ready, we can generate the hash that will be used to compare this deployment with the current one (if it exists). var hash string hash, err = comparison.SetMD5DatadogAgentGenerationAnnotation(&deployment.ObjectMeta, deployment.Spec) if err != nil { @@ -51,23 +54,23 @@ func (r *Reconciler) createOrUpdateDeployment(parentLogger logr.Logger, dda *dat } currentDeployment := &appsv1.Deployment{} - alreadyExist := true + alreadyExists := true err = r.client.Get(context.TODO(), nsName, currentDeployment) if err != nil { if apierrors.IsNotFound(err) { logger.Info("deployment is not found") - alreadyExist = false + alreadyExists = false } else { logger.Error(err, "unexpected error during deployment get") return reconcile.Result{}, err } } - if alreadyExist { + if alreadyExists { // check if same hash needUpdate := !comparison.IsSameSpecMD5Hash(hash, currentDeployment.GetAnnotations()) if !needUpdate { - // no need to update to stop here the process + // no need to update hasn't changed now := metav1.NewTime(time.Now()) updateStatusFunc(currentDeployment, newStatus, now, metav1.ConditionTrue, "deployment_up_to_date", "Deployment up-to-date") return reconcile.Result{}, nil @@ -75,7 +78,7 @@ func (r *Reconciler) createOrUpdateDeployment(parentLogger logr.Logger, dda *dat logger.Info("Updating Deployment") - // TODO: these parameter can be added to the override.PodTemplateSpec. (it exist in v1alpha) + // TODO: these parameters can be added to the override.PodTemplateSpec. (It exists in v1alpha1) keepAnnotationsFilter := "" keepLabelsFilter := "" @@ -119,12 +122,12 @@ func (r *Reconciler) createOrUpdateDaemonset(parentLogger logr.Logger, dda *data var result reconcile.Result var err error - // Set DatadogAgent instance instance as the owner and controller + // Set DatadogAgent instance as the owner and controller if err = controllerutil.SetControllerReference(dda, daemonset, r.scheme); err != nil { return reconcile.Result{}, err } - // From here the PodTemplateSpec should be ready, we can generate the hash that will be use to compare this daemonset with the current (if exist). + // From here the PodTemplateSpec should be ready, we can generate the hash that will be used to compare this daemonset with the current one (if it exists). var hash string hash, err = comparison.SetMD5DatadogAgentGenerationAnnotation(&daemonset.ObjectMeta, daemonset.Spec) if err != nil { @@ -138,35 +141,35 @@ func (r *Reconciler) createOrUpdateDaemonset(parentLogger logr.Logger, dda *data } currentDaemonset := &appsv1.DaemonSet{} - alreadyExist := true + alreadyExists := true err = r.client.Get(context.TODO(), nsName, currentDaemonset) if err != nil { if apierrors.IsNotFound(err) { logger.Info("daemonset is not found") - alreadyExist = false + alreadyExists = false } else { logger.Error(err, "unexpected error during daemonset get") return reconcile.Result{}, err } } - if alreadyExist { + if alreadyExists { // check if same hash needUpdate := !comparison.IsSameSpecMD5Hash(hash, currentDaemonset.GetAnnotations()) if !needUpdate { - // Even if the DaemonSet is still the same, it's status might have + // Even if the DaemonSet is still the same, its status might have // changed (for example, the number of pods ready). This call is // needed to keep the agent status updated. now := metav1.NewTime(time.Now()) newStatus.Agent = datadoghqv2alpha1.UpdateDaemonSetStatus(currentDaemonset, newStatus.Agent, &now) - // no need to update the DaemonSet to stop here the process + // Stop reconcile loop since DaemonSet hasn't changed return reconcile.Result{}, nil } logger.Info("Updating Daemonset") - // TODO: these parameter can be added to the override.PodTemplateSpec. (it exist in v1alpha) + // TODO: these parameters can be added to the override.PodTemplateSpec. (It exists in v1alpha1) keepAnnotationsFilter := "" keepLabelsFilter := "" @@ -181,7 +184,7 @@ func (r *Reconciler) createOrUpdateDaemonset(parentLogger logr.Logger, dda *data if err != nil { return reconcile.Result{}, err } - event := buildEventInfo(updateDaemonset.Name, updateDaemonset.Namespace, deploymentKind, datadog.UpdateEvent) + event := buildEventInfo(updateDaemonset.Name, updateDaemonset.Namespace, daemonSetKind, datadog.UpdateEvent) r.recordEvent(dda, event) updateStatusFunc(updateDaemonset, newStatus, now, metav1.ConditionTrue, "Daemonset_updated", "Daemonset updated") } else { @@ -201,3 +204,92 @@ func (r *Reconciler) createOrUpdateDaemonset(parentLogger logr.Logger, dda *data return result, err } + +func (r *Reconciler) createOrUpdateExtendedDaemonset(parentLogger logr.Logger, dda *datadoghqv2alpha1.DatadogAgent, eds *edsv1alpha1.ExtendedDaemonSet, newStatus *datadoghqv2alpha1.DatadogAgentStatus, updateStatusFunc updateEDSStatusComponentFunc) (reconcile.Result, error) { + logger := parentLogger.WithValues("ExtendedDaemonSet.Namespace", eds.Namespace, "ExtendedDaemonSet.Name", eds.Name) + + var result reconcile.Result + var err error + + // Set DatadogAgent instance as the owner and controller + if err = controllerutil.SetControllerReference(dda, eds, r.scheme); err != nil { + return reconcile.Result{}, err + } + + // From here the PodTemplateSpec should be ready, we can generate the hash that will be used to compare this extendeddaemonset with the current one (if it exists). + var hash string + hash, err = comparison.SetMD5DatadogAgentGenerationAnnotation(&eds.ObjectMeta, eds.Spec) + if err != nil { + return result, err + } + + // Get the current extendeddaemonset and compare + nsName := types.NamespacedName{ + Name: eds.GetName(), + Namespace: eds.GetNamespace(), + } + + currentEDS := &edsv1alpha1.ExtendedDaemonSet{} + alreadyExists := true + err = r.client.Get(context.TODO(), nsName, currentEDS) + if err != nil { + if apierrors.IsNotFound(err) { + logger.Info("ExtendedDaemonSet is not found") + alreadyExists = false + } else { + logger.Error(err, "unexpected error during ExtendedDaemonSet get") + return reconcile.Result{}, err + } + } + + if alreadyExists { + // check if same hash + needUpdate := !comparison.IsSameSpecMD5Hash(hash, currentEDS.GetAnnotations()) + if !needUpdate { + // Even if the EDS is still the same, its status might have + // changed (for example, the number of pods ready). This call is + // needed to keep the agent status updated. + now := metav1.NewTime(time.Now()) + newStatus.Agent = datadoghqv2alpha1.UpdateExtendedDaemonSetStatus(currentEDS, newStatus.Agent, &now) + + // Stop reconcile loop since EDS hasn't changed + return reconcile.Result{}, nil + } + + logger.Info("Updating ExtendedDaemonSet") + + // TODO: these parameters can be added to the override.PodTemplateSpec. (It exists in v1alpha1) + keepAnnotationsFilter := "" + keepLabelsFilter := "" + + // Copy possibly changed fields + updateEDS := eds.DeepCopy() + updateEDS.Spec = *eds.Spec.DeepCopy() + updateEDS.Annotations = mergeAnnotationsLabels(logger, currentEDS.GetAnnotations(), eds.GetAnnotations(), keepAnnotationsFilter) + updateEDS.Labels = mergeAnnotationsLabels(logger, currentEDS.GetLabels(), eds.GetLabels(), keepLabelsFilter) + + now := metav1.NewTime(time.Now()) + err = kubernetes.UpdateFromObject(context.TODO(), r.client, updateEDS, currentEDS.ObjectMeta) + if err != nil { + return reconcile.Result{}, err + } + event := buildEventInfo(updateEDS.Name, updateEDS.Namespace, extendedDaemonSetKind, datadog.UpdateEvent) + r.recordEvent(dda, event) + updateStatusFunc(updateEDS, newStatus, now, metav1.ConditionTrue, "ExtendedDaemonSet_updated", "ExtendedDaemonSet updated") + } else { + now := metav1.NewTime(time.Now()) + + err = r.client.Create(context.TODO(), eds) + if err != nil { + updateStatusFunc(nil, newStatus, now, metav1.ConditionFalse, "create_failed", "Unable to create ExtendedDaemonSet") + return reconcile.Result{}, err + } + event := buildEventInfo(eds.Name, eds.Namespace, extendedDaemonSetKind, datadog.CreationEvent) + r.recordEvent(dda, event) + updateStatusFunc(eds, newStatus, now, metav1.ConditionTrue, "create_success", "ExtendedDaemonSet created") + } + + logger.Info("Creating ExtendedDaemonSet") + + return result, err +}