Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cache): implement not found generic error and use it to fail fast in case cache has an issue #22

Merged
merged 1 commit into from
Dec 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions cache/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,27 @@ import (
configv1alpha1 "github.com/padok-team/burrito/api/v1alpha1"
)

type CacheError struct {
Err error
Nil bool
}

func (c *CacheError) Error() string {
return c.Err.Error()
}

func NotFound(err error) bool {
ce, ok := err.(*CacheError)
if ok {
return ce.Nil
}
return false
}

func (c *CacheError) NotFound() bool {
return c.Nil
}

type Prefix string

const (
Expand Down
9 changes: 5 additions & 4 deletions cache/memory.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package cache

import (
"errors"
)
import "errors"

type MemoryCache struct {
data map[string][]byte
Expand All @@ -16,7 +14,10 @@ func NewMemoryCache() *MemoryCache {

func (m *MemoryCache) Get(key string) ([]byte, error) {
if _, ok := m.data[key]; !ok {
return nil, errors.New("key not found")
return nil, &CacheError{
Err: errors.New("key not found"),
Nil: true,
}
}
return m.data[key], nil
}
Expand Down
12 changes: 11 additions & 1 deletion cache/redis/redis.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"time"

"github.com/go-redis/redis/v8"
"github.com/padok-team/burrito/cache"
)

type Cache struct {
Expand All @@ -23,8 +24,17 @@ func New(addr string, password string, db int) *Cache {

func (c *Cache) Get(key string) ([]byte, error) {
val, err := c.Client.Get(context.TODO(), key).Result()
if err == redis.Nil {
return nil, &cache.CacheError{
Err: err,
Nil: true,
}
}
if err != nil {
return nil, err
return nil, &cache.CacheError{
Err: err,
Nil: false,
}
}
return []byte(val), nil
}
Expand Down
89 changes: 64 additions & 25 deletions controllers/terraformlayer_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,34 @@ type TerraformLayerConditions struct {
}

func (t *TerraformLayerConditions) Evaluate() (func(ctx context.Context, c client.Client) ctrl.Result, []metav1.Condition) {
isTerraformRunning := t.IsRunning.Evaluate(*t.Cache, t.Resource)
isPlanArtifactUpToDate := t.IsPlanArtifactUpToDate.Evaluate(*t.Cache, t.Resource)
isApplyUpToDate := t.IsApplyUpToDate.Evaluate(*t.Cache, t.Resource)
hasTerraformFailed := t.HasFailed.Evaluate(*t.Cache, t.Resource)
isTerraformRunning, err := t.IsRunning.Evaluate(*t.Cache, t.Resource)
if err != nil {
log.Log.Info("Something went wrong with conditions evaluation requeuing")
return func(ctx context.Context, c client.Client) ctrl.Result {
return ctrl.Result{RequeueAfter: time.Minute * 2}
}, nil
}
isPlanArtifactUpToDate, err := t.IsPlanArtifactUpToDate.Evaluate(*t.Cache, t.Resource)
if err != nil {
log.Log.Info("Something went wrong with conditions evaluation requeuing")
return func(ctx context.Context, c client.Client) ctrl.Result {
return ctrl.Result{RequeueAfter: time.Minute * 2}
}, nil
}
isApplyUpToDate, err := t.IsApplyUpToDate.Evaluate(*t.Cache, t.Resource)
if err != nil {
log.Log.Info("Something went wrong with conditions evaluation requeuing")
return func(ctx context.Context, c client.Client) ctrl.Result {
return ctrl.Result{RequeueAfter: time.Minute * 2}
}, nil
}
hasTerraformFailed, err := t.HasFailed.Evaluate(*t.Cache, t.Resource)
if err != nil {
log.Log.Info("Something went wrong with conditions evaluation requeuing")
return func(ctx context.Context, c client.Client) ctrl.Result {
return ctrl.Result{RequeueAfter: time.Minute * 2}
}, nil
}
conditions := []metav1.Condition{t.IsRunning.Condition, t.IsPlanArtifactUpToDate.Condition, t.IsApplyUpToDate.Condition, t.HasFailed.Condition}
cache := *t.Cache
switch {
Expand Down Expand Up @@ -217,43 +241,49 @@ type TerraformRunning struct {
Condition metav1.Condition
}

func (c *TerraformRunning) Evaluate(cache internal.Cache, t *configv1alpha1.TerraformLayer) bool {
func (c *TerraformRunning) Evaluate(cache internal.Cache, t *configv1alpha1.TerraformLayer) (bool, error) {
c.Condition = metav1.Condition{
Type: IsRunning,
ObservedGeneration: t.GetObjectMeta().GetGeneration(),
Status: metav1.ConditionUnknown,
}
key := internal.GenerateKey(internal.Lock, t)
_, err := cache.Get(key)
if err != nil {
if internal.NotFound(err) {
c.Condition.Reason = "NoLockInCache"
c.Condition.Message = "No lock has been found in Cache. Terraform is not running on this layer."
c.Condition.Status = metav1.ConditionFalse
return false
return false, nil
}
if err != nil {
return true, err
}
c.Condition.Reason = "LockInCache"
c.Condition.Message = "Lock has been found in Cache. Terraform is already running on this layer."
c.Condition.Status = metav1.ConditionTrue
return true
return true, nil
}

type TerraformPlanArtifactUpToDate struct {
Condition metav1.Condition
}

func (c *TerraformPlanArtifactUpToDate) Evaluate(cache internal.Cache, t *configv1alpha1.TerraformLayer) bool {
func (c *TerraformPlanArtifactUpToDate) Evaluate(cache internal.Cache, t *configv1alpha1.TerraformLayer) (bool, error) {
c.Condition = metav1.Condition{
Type: IsPlanArtifactUpToDate,
ObservedGeneration: t.GetObjectMeta().GetGeneration(),
Status: metav1.ConditionUnknown,
}
key := internal.GenerateKey(internal.LastPlanDate, t)
value, err := cache.Get(key)
if err != nil {
if internal.NotFound(err) {
c.Condition.Reason = "NoTimestampInCache"
c.Condition.Message = "The last plan date is not in cache."
c.Condition.Status = metav1.ConditionFalse
return false
return false, nil
}
if err != nil {
return true, err
}
unixTimestamp, _ := strconv.ParseInt(string(value), 10, 64)
lastPlanDate := time.Unix(unixTimestamp, 0)
Expand All @@ -263,75 +293,84 @@ func (c *TerraformPlanArtifactUpToDate) Evaluate(cache internal.Cache, t *config
c.Condition.Reason = "PlanIsRecent"
c.Condition.Message = "The plan has been made less than 20 minutes ago."
c.Condition.Status = metav1.ConditionTrue
return true
return true, nil
}
c.Condition.Reason = "PlanIsTooOld"
c.Condition.Message = "The plan has been made more than 20 minutes ago."
c.Condition.Status = metav1.ConditionFalse
return false
return false, nil
}

type TerraformApplyUpToDate struct {
Condition metav1.Condition
}

func (c *TerraformApplyUpToDate) Evaluate(cache internal.Cache, t *configv1alpha1.TerraformLayer) bool {
func (c *TerraformApplyUpToDate) Evaluate(cache internal.Cache, t *configv1alpha1.TerraformLayer) (bool, error) {
c.Condition = metav1.Condition{
Type: IsApplyUpToDate,
ObservedGeneration: t.GetObjectMeta().GetGeneration(),
Status: metav1.ConditionUnknown,
}
key := internal.GenerateKey(internal.LastPlannedArtifact, t)
planHash, err := cache.Get(key)
if err != nil {
if internal.NotFound(err) {
c.Condition.Reason = "NoPlanYet"
c.Condition.Message = "No plan has run yet, Layer might be new"
c.Condition.Status = metav1.ConditionTrue
return true
return true, nil
}
if err != nil {
return true, err
}
key = internal.GenerateKey(internal.LastAppliedArtifact, t)
applyHash, err := cache.Get(key)
if err != nil {
if internal.NotFound(err) {
c.Condition.Reason = "NoApplyHasRan"
c.Condition.Message = "Apply has not ran yet but a plan is available, launching apply"
c.Condition.Status = metav1.ConditionFalse
return false
return false, nil
}
if err != nil {
return true, err
}
if bytes.Compare(planHash, applyHash) != 0 {
c.Condition.Reason = "NewPlanAvailable"
c.Condition.Message = "Apply will run."
c.Condition.Status = metav1.ConditionFalse
return false
return false, nil
}
c.Condition.Reason = "ApplyUpToDate"
c.Condition.Message = "Last planned artifact is the same as the last applied one"
c.Condition.Status = metav1.ConditionTrue
return true
return true, nil
}

type TerraformFailure struct {
Condition metav1.Condition
}

func (c *TerraformFailure) Evaluate(cache internal.Cache, t *configv1alpha1.TerraformLayer) bool {
func (c *TerraformFailure) Evaluate(cache internal.Cache, t *configv1alpha1.TerraformLayer) (bool, error) {
c.Condition = metav1.Condition{
Type: HasFailed,
ObservedGeneration: t.GetObjectMeta().GetGeneration(),
Status: metav1.ConditionUnknown,
}
key := internal.GenerateKey(internal.RunResult, t)
result, err := cache.Get(key)
if err != nil {
if internal.NotFound(err) {
c.Condition.Reason = "NoRunYet"
c.Condition.Message = "Terraform has not ran yet"
c.Condition.Status = metav1.ConditionFalse
return false
return false, nil
}
if err != nil {
return true, err
}
if string(result) == "0" {
c.Condition.Reason = "RunExitedGracefully"
c.Condition.Message = "Last run exited gracefully"
c.Condition.Status = metav1.ConditionFalse
return false
return false, nil
}
c.Condition.Status = metav1.ConditionTrue
key = internal.GenerateKey(internal.RunMessage, t)
Expand All @@ -342,5 +381,5 @@ func (c *TerraformFailure) Evaluate(cache internal.Cache, t *configv1alpha1.Terr
}
c.Condition.Reason = "TerraformRunFailure"
c.Condition.Message = string(message)
return true
return true, nil
}