From 12f47b1fa788530758047d5cfcb7ed9ae39c2efa Mon Sep 17 00:00:00 2001 From: Heba Elayoty Date: Tue, 4 Apr 2023 16:39:03 -0700 Subject: [PATCH 1/3] Use old data with failure message in case APIs has error Signed-off-by: Heba Elayoty --- pkg/exporter/configmap.go | 50 +++++++++++++++++++++++------------- pkg/exporter/consts.go | 2 +- pkg/exporter/exporter.go | 53 +++++++++++++++++++++++++++++++++++---- 3 files changed, 82 insertions(+), 23 deletions(-) diff --git a/pkg/exporter/configmap.go b/pkg/exporter/configmap.go index c4a26f6..93b117f 100644 --- a/pkg/exporter/configmap.go +++ b/pkg/exporter/configmap.go @@ -25,7 +25,7 @@ var ( isImmutable = true ) -func (e *Exporter) CreateOrUpdateConfigMap(ctx context.Context, configMapName string, emissionForecast []client.EmissionsForecastDto) error { +func (e *Exporter) CreateConfigMapFromEmissionForecast(ctx context.Context, configMapName string, emissionForecast []client.EmissionsForecastDto) error { if emissionForecast == nil { return errors.New("emission forecast cannot be nil") } @@ -41,33 +41,36 @@ func (e *Exporter) CreateOrUpdateConfigMap(ctx context.Context, configMapName st minForecast, maxForeCast := getMinMaxForecast(ctx, forecast.ForecastData) - configMap := &corev1.ConfigMap{ - ObjectMeta: v1.ObjectMeta{ - Name: configMapName, - Namespace: client.Namespace, - }, - Immutable: &isImmutable, - Data: map[string]string{ + return e.CreateConfigMapFromProperties(ctx, configMapName, + map[string]string{ ConfigMapLastHeartbeatTime: time.Now().String(), // The latest time that the data exporter controller sends the data. ConfigMapMessage: "", // Additional information for user notification, if any. ConfigMapNumOfRecords: strconv.Itoa(len(forecast.ForecastData)), // The number can be any value between 0 (no records for the current location) and 24(hours) * 12(5 min interval per hour). ConfigMapForecastDateTime: forecast.DataStartAt.String(), // The time when the data was started by the GSF SDK. ConfigMapMinForecast: fmt.Sprintf("%f", minForecast), // min forecast in the forecastData. ConfigMapMaxForecast: fmt.Sprintf("%f", maxForeCast), // max forecast in the forecastData. + }, binaryData) +} + +func (e *Exporter) CreateConfigMapFromProperties(ctx context.Context, configMapName string, data map[string]string, binaryData []byte) error { + configMap := &corev1.ConfigMap{ + ObjectMeta: v1.ObjectMeta{ + Name: configMapName, + Namespace: client.Namespace, }, + Immutable: &isImmutable, + Data: data, BinaryData: map[string][]byte{ - "data": binaryData, // json marshal of the EmissionsData array. + BinaryData: binaryData, // json marshal of the EmissionsData array. }, } - - _, err = e.clusterClient.CoreV1(). + _, err := e.clusterClient.CoreV1(). ConfigMaps(client.Namespace). Create(ctx, configMap, v1.CreateOptions{}) if err != nil { return err } klog.Infof("configMap %s has been created", configMapName) - return nil } @@ -83,15 +86,16 @@ func (e *Exporter) GetGonfigMapWatch(ctx context.Context, configMapName string) return watch } -func (e *Exporter) DeleteConfigmap(ctx context.Context, configMapName string) error { - _, err := e.clusterClient.CoreV1().ConfigMaps(client.Namespace).Get(ctx, configMapName, v1.GetOptions{}) +func (e *Exporter) DeleteConfigMap(ctx context.Context, configMapName string) error { + currentConfigMap, err := e.GetConfigMap(ctx, configMapName) if err != nil { - if apierrors.IsNotFound(err) { // if configMap is not found, no errors will be returned. - return nil - } return err } + if currentConfigMap == nil { + return nil // configMap is not found, delete will not be called. + } + err = e.clusterClient.CoreV1(). ConfigMaps(client.Namespace). Delete(ctx, configMapName, v1.DeleteOptions{}) @@ -104,6 +108,18 @@ func (e *Exporter) DeleteConfigmap(ctx context.Context, configMapName string) er return nil } +func (e *Exporter) GetConfigMap(ctx context.Context, configMapName string) (*corev1.ConfigMap, error) { + currentConfigMap, err := e.clusterClient.CoreV1().ConfigMaps(client.Namespace).Get(ctx, configMapName, v1.GetOptions{}) + if err != nil { + if apierrors.IsNotFound(err) { // if configMap is not found, no errors will be returned. + return nil, nil + } + return nil, err + } + + return currentConfigMap, nil +} + func getMinMaxForecast(ctx context.Context, forecastData []client.EmissionsDataDto) (float64, float64) { values := make([]float64, len(forecastData)) for index := range forecastData { diff --git a/pkg/exporter/consts.go b/pkg/exporter/consts.go index b41fad5..42d009b 100644 --- a/pkg/exporter/consts.go +++ b/pkg/exporter/consts.go @@ -14,5 +14,5 @@ const ( ConfigMapMinForecast = "minForecast" ConfigMapMaxForecast = "maxForecast" - TimeLayout = "2006-01-02 15:04:05" + BinaryData = "data" ) diff --git a/pkg/exporter/exporter.go b/pkg/exporter/exporter.go index 539ff84..d356b35 100644 --- a/pkg/exporter/exporter.go +++ b/pkg/exporter/exporter.go @@ -105,19 +105,43 @@ func (e *Exporter) Run(ctx context.Context, configMapName, region string, patrol } func (e *Exporter) RefreshData(ctx context.Context, configMapName string, region string, stopChan <-chan struct{}) error { - err := e.DeleteConfigmap(ctx, configMapName) + // get current object (if any) in case we could not update the data. + currentConfigMap, err := e.GetConfigMap(ctx, configMapName) + if err != nil { + return err + } + + err = e.DeleteConfigMap(ctx, configMapName) if err != nil && !apierrors.IsNotFound(err) { // if configMap is not found, return err } - retry.OnError(constantBackoff, func(err error) bool { + var forecast []client.EmissionsForecastDto + err = retry.OnError(constantBackoff, func(err error) bool { return true }, func() error { - forecast, err := e.getCurrentForecastData(ctx, region, stopChan) - if err != nil { + forecast, err = e.getCurrentForecastData(ctx, region, stopChan) + return err + }) + if err != nil { + if currentConfigMap != nil { + // return old data with failed message + return e.UseCurrentConfigMap(ctx, currentConfigMap) + } else { + e.recorder.Eventf(&corev1.ObjectReference{ + Kind: "Pod", + Namespace: client.Namespace, + Name: client.PodName, + }, corev1.EventTypeWarning, "Cannot retrieve updated forecast data", "Error while retrieving updated forecast data") + klog.Errorf("an error has occurred while retrieving updated forecast data") return err } - return e.CreateOrUpdateConfigMap(ctx, configMapName, forecast) + } + + err = retry.OnError(constantBackoff, func(err error) bool { + return true + }, func() error { + return e.CreateConfigMapFromEmissionForecast(ctx, configMapName, forecast) }) if err != nil { e.recorder.Eventf(&corev1.ObjectReference{ @@ -136,6 +160,25 @@ func (e *Exporter) RefreshData(ctx context.Context, configMapName string, region return nil } +func (e *Exporter) UseCurrentConfigMap(ctx context.Context, currentConfigMap *corev1.ConfigMap) error { + if currentConfigMap.Data != nil { + currentConfigMap.Data[ConfigMapLastHeartbeatTime] = time.Now().String() + currentConfigMap.Data[ConfigMapMessage] = "Unable to update forecast Data." + } else { + currentConfigMap.Data = map[string]string{ + ConfigMapLastHeartbeatTime: time.Now().String(), + ConfigMapMessage: "Unable to update forecast Data.", + } + } + if currentConfigMap.BinaryData == nil { + currentConfigMap.BinaryData = map[string][]byte{ + BinaryData: {}, + } + } + return e.CreateConfigMapFromProperties(ctx, currentConfigMap.Name, + currentConfigMap.Data, currentConfigMap.BinaryData[BinaryData]) +} + func (e *Exporter) getCurrentForecastData(ctx context.Context, region string, stopChan <-chan struct{}) ([]client.EmissionsForecastDto, error) { opt := &client.CarbonAwareApiGetCurrentForecastDataOpts{ DataStartAt: optional.EmptyTime(), From d76194f981208c6a6ec3ce4d18f3c12e2789adae Mon Sep 17 00:00:00 2001 From: Heba Elayoty Date: Wed, 5 Apr 2023 14:05:06 -0700 Subject: [PATCH 2/3] Pass error message to the configMap Signed-off-by: Heba Elayoty --- pkg/exporter/exporter.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/exporter/exporter.go b/pkg/exporter/exporter.go index d356b35..e9046e9 100644 --- a/pkg/exporter/exporter.go +++ b/pkg/exporter/exporter.go @@ -126,7 +126,7 @@ func (e *Exporter) RefreshData(ctx context.Context, configMapName string, region if err != nil { if currentConfigMap != nil { // return old data with failed message - return e.UseCurrentConfigMap(ctx, currentConfigMap) + return e.UseCurrentConfigMap(ctx, err.Error(), currentConfigMap) } else { e.recorder.Eventf(&corev1.ObjectReference{ Kind: "Pod", @@ -160,14 +160,14 @@ func (e *Exporter) RefreshData(ctx context.Context, configMapName string, region return nil } -func (e *Exporter) UseCurrentConfigMap(ctx context.Context, currentConfigMap *corev1.ConfigMap) error { +func (e *Exporter) UseCurrentConfigMap(ctx context.Context, message string, currentConfigMap *corev1.ConfigMap) error { if currentConfigMap.Data != nil { currentConfigMap.Data[ConfigMapLastHeartbeatTime] = time.Now().String() currentConfigMap.Data[ConfigMapMessage] = "Unable to update forecast Data." } else { currentConfigMap.Data = map[string]string{ ConfigMapLastHeartbeatTime: time.Now().String(), - ConfigMapMessage: "Unable to update forecast Data.", + ConfigMapMessage: message, } } if currentConfigMap.BinaryData == nil { From 43bc45ba66c3dde29867dfae70326f6b6ed0dd00 Mon Sep 17 00:00:00 2001 From: Heba Elayoty Date: Wed, 5 Apr 2023 14:12:38 -0700 Subject: [PATCH 3/3] Pass error message to the configMap Signed-off-by: Heba Elayoty --- pkg/exporter/exporter.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/exporter/exporter.go b/pkg/exporter/exporter.go index e9046e9..cf70f08 100644 --- a/pkg/exporter/exporter.go +++ b/pkg/exporter/exporter.go @@ -163,7 +163,7 @@ func (e *Exporter) RefreshData(ctx context.Context, configMapName string, region func (e *Exporter) UseCurrentConfigMap(ctx context.Context, message string, currentConfigMap *corev1.ConfigMap) error { if currentConfigMap.Data != nil { currentConfigMap.Data[ConfigMapLastHeartbeatTime] = time.Now().String() - currentConfigMap.Data[ConfigMapMessage] = "Unable to update forecast Data." + currentConfigMap.Data[ConfigMapMessage] = message } else { currentConfigMap.Data = map[string]string{ ConfigMapLastHeartbeatTime: time.Now().String(),