Skip to content

Commit

Permalink
feat: Use old configMap data with failure message in case APIs has er…
Browse files Browse the repository at this point in the history
…ror (#13)

Signed-off-by: Heba Elayoty <hebaelayoty@gmail.com>
  • Loading branch information
helayoty committed Apr 5, 2023
1 parent 3e8dd74 commit 862f103
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 23 deletions.
50 changes: 33 additions & 17 deletions pkg/exporter/configmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand All @@ -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
}

Expand All @@ -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{})
Expand All @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion pkg/exporter/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@ const (
ConfigMapMinForecast = "minForecast"
ConfigMapMaxForecast = "maxForecast"

TimeLayout = "2006-01-02 15:04:05"
BinaryData = "data"
)
53 changes: 48 additions & 5 deletions pkg/exporter/exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -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, err.Error(), 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{
Expand All @@ -136,6 +160,25 @@ func (e *Exporter) RefreshData(ctx context.Context, configMapName string, region
return nil
}

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] = message
} else {
currentConfigMap.Data = map[string]string{
ConfigMapLastHeartbeatTime: time.Now().String(),
ConfigMapMessage: message,
}
}
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(),
Expand Down

0 comments on commit 862f103

Please sign in to comment.