package edgedb import ( "context" "fmt" "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" bannerApi "edge-infra.dev/pkg/edge/apis/banner/v1alpha1" clusterApi "edge-infra.dev/pkg/edge/apis/cluster/v1alpha1" gkeclusterApi "edge-infra.dev/pkg/edge/apis/gkecluster/v1alpha1" "edge-infra.dev/pkg/edge/controllers/dbmetrics" "edge-infra.dev/pkg/k8s/meta/status" "edge-infra.dev/pkg/k8s/runtime/conditions" "edge-infra.dev/pkg/lib/fog" ) // InfraStatus is an enum whose values are written to the `infra_status` column in the `clusters` & `banners` tables. type InfraStatus string const ( InfraStatusReady InfraStatus = "READY" InfraStatusError InfraStatus = "ERROR" // Provisioning is the default value when rows are created. The db status util does not set this value. InfraStatusProvisioning InfraStatus = "PROVISIONING" ) const errorLogMessage = "Infra status not recorded" const sqlUpdateClusterStatus = "UPDATE clusters SET infra_status=$1, infra_status_details=$2, infra_status_updated_at=$3 WHERE cluster_edge_id=$4" const sqlUpdateBannerStatus = "UPDATE banners SET infra_status=$1, infra_status_details=$2, infra_status_updated_at=$3 WHERE banner_edge_id=$4" // Record takes a Banner, Cluster, or GKECluster object as input, and writes its infra status to the database. Errors are logged using the passed in context. // // NOTE: To make integration testing easier for consumers, this function safely returns, without panicing, when the wrapped DB is nil. func (edb *EdgeDB) RecordInfraStatus(ctx context.Context, obj conditions.Getter, recorder dbmetrics.DBMetrics) { var log = fog.FromContext(ctx).WithName("dbinfrastatus") if edb.DB == nil { // Quietly exit when the wrapped DB is nil. return } // choose the sql statement based on the object's kind var stmt string kind := obj.GetObjectKind().GroupVersionKind().Kind switch kind { case clusterApi.Kind, gkeclusterApi.Kind: stmt = sqlUpdateClusterStatus case bannerApi.BannerKind: stmt = sqlUpdateBannerStatus default: err := fmt.Errorf("Kind not supported") log.Error(err, errorLogMessage, "kind", kind) return } var cv = columnValuesForObject(obj) _, err := edb.DB.ExecContext(ctx, stmt, cv.InfraStatus, cv.InfraStatusDetails, cv.InfraStatusUpdatedAt, cv.EdgeID) if err != nil { log.Error(err, errorLogMessage) recorder.RecordDBErrorsTotal(ctx, kind, obj.GetName()) } else { recorder.RecordDBStatusWritesTotal(ctx, kind, toString(cv.InfraStatus)) } } type columnValues struct { EdgeID string InfraStatus InfraStatus InfraStatusDetails string InfraStatusUpdatedAt time.Time } func conditionStatusToInfraStatus(v metav1.ConditionStatus) InfraStatus { if v == metav1.ConditionTrue { return InfraStatusReady } else if v == metav1.ConditionFalse { return InfraStatusError } return InfraStatusProvisioning } func columnValuesForObject(obj conditions.Getter) columnValues { for _, cs := range obj.GetConditions() { if cs.Type == status.ReadyCondition { return columnValues{ EdgeID: obj.GetName(), InfraStatus: conditionStatusToInfraStatus(cs.Status), InfraStatusDetails: fmt.Sprintf("%s: %s", cs.Reason, cs.Message), InfraStatusUpdatedAt: cs.LastTransitionTime.Time, } } } return columnValues{ EdgeID: obj.GetName(), InfraStatus: InfraStatusProvisioning, InfraStatusDetails: "Ready condition not found", InfraStatusUpdatedAt: time.Now(), } } func toString(infraStatus InfraStatus) string { switch infraStatus { case InfraStatusReady: return "Ready" case InfraStatusError: return "Error" case InfraStatusProvisioning: return "Provisioning" default: return "Unknown" } }