Skip to content
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
47 changes: 33 additions & 14 deletions cmd/deprovisionretrigger/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ type Config struct {
DryRun bool `envconfig:"default=true"`
}

type Result struct {
instancesToDeprovisionAgain int
deprovisioningAccepted int
sanityFailedCount int
failuresCount int
}

type DeprovisionRetriggerService struct {
cfg Config
instanceStorage storage.Instances
Expand Down Expand Up @@ -59,10 +66,17 @@ func main() {
fatalOnError(err)
svc := newDeprovisionRetriggerService(cfg, brokerClient, db.Instances())

err = svc.PerformCleanup()

result, err := svc.PerformCleanup()
fatalOnError(err)

slog.Info(fmt.Sprintf(
"Out of %d instances to retrigger deprovisioning: accepted requests = %d, skipped due to sanity failed = %d, failed requests = %d",
result.instancesToDeprovisionAgain,
result.deprovisioningAccepted,
result.sanityFailedCount,
result.failuresCount,
))

slog.Info("Deprovision retrigger job finished successfully!")
err = conn.Close()
if err != nil {
Expand All @@ -84,28 +98,33 @@ func newDeprovisionRetriggerService(cfg Config, brokerClient BrokerClient, insta
}
}

func (s *DeprovisionRetriggerService) PerformCleanup() error {
func (s *DeprovisionRetriggerService) PerformCleanup() (Result, error) {
notCompletelyDeletedFilter := dbmodel.InstanceFilter{DeletionAttempted: &[]bool{true}[0]}
instancesToDeprovisionAgain, _, _, err := s.instanceStorage.List(notCompletelyDeletedFilter)

if err != nil {
return fmt.Errorf("while getting not completely deprovisioned instances: %w", err)
return Result{}, fmt.Errorf("while getting not completely deprovisioned instances: %w", err)
}

if s.cfg.DryRun {
s.logInstances(instancesToDeprovisionAgain)
slog.Info(
"Dry run completed",
"instancesToRetrigger", len(instancesToDeprovisionAgain),
)
} else {
failuresCount, sanityFailedCount := s.retriggerDeprovisioningForInstances(instancesToDeprovisionAgain)
deprovisioningAccepted := len(instancesToDeprovisionAgain) - failuresCount - sanityFailedCount
slog.Info(fmt.Sprintf("Out of %d instances to retrigger deprovisioning: accepted requests = %d, skipped due to sanity failed = %d, failed requests = %d",
len(instancesToDeprovisionAgain), deprovisioningAccepted, sanityFailedCount, failuresCount))
return Result{
instancesToDeprovisionAgain: len(instancesToDeprovisionAgain),
deprovisioningAccepted: 0,
sanityFailedCount: 0,
failuresCount: 0,
}, nil
}

return nil
failuresCount, sanityFailedCount := s.retriggerDeprovisioningForInstances(instancesToDeprovisionAgain)
deprovisioningAccepted := len(instancesToDeprovisionAgain) - failuresCount - sanityFailedCount

return Result{
instancesToDeprovisionAgain: len(instancesToDeprovisionAgain),
deprovisioningAccepted: deprovisioningAccepted,
sanityFailedCount: sanityFailedCount,
failuresCount: failuresCount,
}, nil
}

func (s *DeprovisionRetriggerService) retriggerDeprovisioningForInstances(instances []internal.Instance) (int, int) {
Expand Down
166 changes: 166 additions & 0 deletions cmd/deprovisionretrigger/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package main

import (
"fmt"
"io"
"net/http"
"os"
"strings"
"testing"
"time"

"github.com/kyma-project/kyma-environment-broker/internal"
"github.com/kyma-project/kyma-environment-broker/internal/fixture"
"github.com/kyma-project/kyma-environment-broker/internal/storage"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestMain(m *testing.M) {
exitVal := 0
defer func() {
os.Exit(exitVal)
}()

docker, err := internal.NewDockerHandler()
fatalOnError(err)
defer func() {
fatalOnError(docker.CloseDockerClient())
}()

config := deprovisionRetriggerTestConfig()
cleanupContainer, err := docker.CreateDBContainer(internal.ContainerCreateRequest{
Port: config.Port,
User: config.User,
Password: config.Password,
Name: config.Name,
Host: config.Host,
ContainerName: "test-deprovision-retrigger",
Image: internal.PostgresImage,
})
fatalOnError(err)
defer func() {
fatalOnError(cleanupContainer())
}()

exitVal = m.Run()
}

func TestCleanup(t *testing.T) {
for tn, tc := range map[string]struct {
dryRun bool
statusCode int
deprovisionErr error
expectedResult Result
}{
"should retrigger deprovisioning for deleted instance": {
dryRun: false,
statusCode: http.StatusNotFound,
deprovisionErr: nil,
expectedResult: Result{
instancesToDeprovisionAgain: 1,
deprovisioningAccepted: 1,
sanityFailedCount: 0,
failuresCount: 0,
},
},
"should not retrigger deprovisioning for deleted instance in dry run mode": {
dryRun: true,
statusCode: http.StatusNotFound,
deprovisionErr: nil,
expectedResult: Result{
instancesToDeprovisionAgain: 1,
deprovisioningAccepted: 0,
sanityFailedCount: 0,
failuresCount: 0,
},
},
"should not retrigger deprovisioning when sanity returns 200": {
dryRun: false,
statusCode: http.StatusOK,
deprovisionErr: nil,
expectedResult: Result{
instancesToDeprovisionAgain: 1,
deprovisioningAccepted: 0,
sanityFailedCount: 1,
failuresCount: 0,
},
},
"should count failure when deprovisioning returns error": {
dryRun: false,
statusCode: http.StatusNotFound,
deprovisionErr: fmt.Errorf("deprovision failed"),
expectedResult: Result{
instancesToDeprovisionAgain: 1,
deprovisioningAccepted: 0,
sanityFailedCount: 0,
failuresCount: 1,
},
},
} {
t.Run(tn, func(t *testing.T) {
storageCleanup, db, err := storage.GetStorageForTests(deprovisionRetriggerTestConfig())
require.NoError(t, err)
defer func() {
assert.NoError(t, storageCleanup())
}()

instance := fixture.FixInstance("i1")
instance.DeletedAt = time.Now()
err = db.Instances().Insert(instance)
require.NoError(t, err)

op := fixture.FixProvisioningOperation("o1", "i1")
err = db.Operations().InsertOperation(op)
require.NoError(t, err)

err = db.Instances().UpdateInstanceLastOperation("i1", "o1")
require.NoError(t, err)

svc := newDeprovisionRetriggerService(
Config{DryRun: tc.dryRun},
&mockBrokerClient{statusCode: tc.statusCode, deprovisionErr: tc.deprovisionErr},
db.Instances(),
)

result, err := svc.PerformCleanup()
assert.NoError(t, err)
assert.Equal(t, tc.expectedResult, result)
})
}
}

func deprovisionRetriggerTestConfig() storage.Config {
return storage.Config{
Host: "localhost",
User: "test",
Password: "FIPS-compl1antPwd!",
Port: "5434",
Name: "test-deprovision-retrigger",
SSLMode: "disable",
SecretKey: "################################",
MaxOpenConns: 4,
MaxIdleConns: 2,
ConnMaxLifetime: 4 * time.Minute,
}
}

type mockBrokerClient struct {
statusCode int
deprovisionErr error
}

func (m *mockBrokerClient) Deprovision(instance internal.Instance) (string, error) {
if m.deprovisionErr != nil {
return "", m.deprovisionErr
}
return "op-id", nil
}

func (m *mockBrokerClient) GetInstanceRequest(instanceID string) (*http.Response, error) {
return &http.Response{
StatusCode: m.statusCode,
Body: io.NopCloser(strings.NewReader("")),
}, nil
}
Loading