github.com/kyma-project/kyma-environment-broker@v0.0.1/cmd/trialcleanup/main.go (about)

     1  package main
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"time"
     8  
     9  	"github.com/kyma-project/control-plane/components/schema-migrator/cleaner"
    10  	"github.com/kyma-project/kyma-environment-broker/internal"
    11  	"github.com/kyma-project/kyma-environment-broker/internal/broker"
    12  	"github.com/kyma-project/kyma-environment-broker/internal/events"
    13  	"github.com/kyma-project/kyma-environment-broker/internal/storage"
    14  	"github.com/kyma-project/kyma-environment-broker/internal/storage/dbmodel"
    15  	log "github.com/sirupsen/logrus"
    16  	"github.com/vrischmann/envconfig"
    17  )
    18  
    19  const (
    20  	trialPlanID = broker.TrialPlanID
    21  )
    22  
    23  type BrokerClient interface {
    24  	SendExpirationRequest(instance internal.Instance) (bool, error)
    25  }
    26  
    27  type Config struct {
    28  	Database         storage.Config
    29  	Broker           broker.ClientConfig
    30  	DryRun           bool          `envconfig:"default=true"`
    31  	ExpirationPeriod time.Duration `envconfig:"default=336h"`
    32  }
    33  
    34  type TrialCleanupService struct {
    35  	cfg             Config
    36  	filter          dbmodel.InstanceFilter
    37  	instanceStorage storage.Instances
    38  	brokerClient    BrokerClient
    39  }
    40  
    41  type instancePredicate func(internal.Instance) bool
    42  
    43  func main() {
    44  	log.SetFormatter(&log.JSONFormatter{})
    45  	log.Info("Starting trial cleanup job")
    46  
    47  	// create and fill config
    48  	var cfg Config
    49  	err := envconfig.InitWithPrefix(&cfg, "APP")
    50  	fatalOnError(err)
    51  
    52  	if cfg.DryRun {
    53  		log.Info("Dry run only - no changes")
    54  	}
    55  
    56  	log.Infof("Expiration period: %+v", cfg.ExpirationPeriod)
    57  
    58  	ctx := context.Background()
    59  	brokerClient := broker.NewClient(ctx, cfg.Broker)
    60  
    61  	// create storage connection
    62  	cipher := storage.NewEncrypter(cfg.Database.SecretKey)
    63  	db, conn, err := storage.NewFromConfig(cfg.Database, events.Config{}, cipher, log.WithField("service", "storage"))
    64  	fatalOnError(err)
    65  	svc := newTrialCleanupService(cfg, brokerClient, db.Instances())
    66  
    67  	err = svc.PerformCleanup()
    68  
    69  	fatalOnError(err)
    70  
    71  	log.Info("Trial cleanup job finished successfully!")
    72  
    73  	err = conn.Close()
    74  	if err != nil {
    75  		fatalOnError(err)
    76  	}
    77  
    78  	cleaner.HaltIstioSidecar()
    79  	// do not use defer, close must be done before halting
    80  	err = cleaner.Halt()
    81  	fatalOnError(err)
    82  }
    83  
    84  func newTrialCleanupService(cfg Config, brokerClient BrokerClient, instances storage.Instances) *TrialCleanupService {
    85  	return &TrialCleanupService{
    86  		cfg:             cfg,
    87  		instanceStorage: instances,
    88  		brokerClient:    brokerClient,
    89  	}
    90  }
    91  
    92  func (s *TrialCleanupService) PerformCleanup() error {
    93  
    94  	trialInstancesFilter := dbmodel.InstanceFilter{PlanIDs: []string{trialPlanID}}
    95  	trialInstances, trialInstancesCount, err := s.getInstances(trialInstancesFilter)
    96  
    97  	if err != nil {
    98  		log.Error(fmt.Sprintf("while getting trial instances: %s", err))
    99  		return err
   100  	}
   101  
   102  	instancesToExpire, instancesToExpireCount := s.filterInstances(
   103  		trialInstances,
   104  		func(instance internal.Instance) bool { return time.Since(instance.CreatedAt) >= s.cfg.ExpirationPeriod },
   105  	)
   106  
   107  	instancesToBeLeftCount := trialInstancesCount - instancesToExpireCount
   108  
   109  	if s.cfg.DryRun {
   110  		s.logInstances(instancesToExpire)
   111  		log.Infof("Trials: %+v, to expire now: %+v, to be left non-expired: %+v", trialInstancesCount, instancesToExpireCount, instancesToBeLeftCount)
   112  	} else {
   113  		suspensionsAcceptedCount, onlyMarkedAsExpiredCount, failuresCount := s.cleanupInstances(instancesToExpire)
   114  		log.Infof("Trials: %+v, to expire: %+v, left non-expired: %+v, suspension under way: %+v just marked expired: %+v, failures: %+v", trialInstancesCount, instancesToExpireCount, instancesToBeLeftCount, suspensionsAcceptedCount, onlyMarkedAsExpiredCount, failuresCount)
   115  	}
   116  	return nil
   117  }
   118  
   119  func (s *TrialCleanupService) getInstances(filter dbmodel.InstanceFilter) ([]internal.Instance, int, error) {
   120  
   121  	instances, _, totalCount, err := s.instanceStorage.List(filter)
   122  	if err != nil {
   123  		return []internal.Instance{}, 0, err
   124  	}
   125  
   126  	return instances, totalCount, nil
   127  }
   128  
   129  func (s *TrialCleanupService) filterInstances(instances []internal.Instance, filter instancePredicate) ([]internal.Instance, int) {
   130  	var filteredInstances []internal.Instance
   131  	for _, instance := range instances {
   132  		if filter(instance) {
   133  			filteredInstances = append(filteredInstances, instance)
   134  		}
   135  	}
   136  	return filteredInstances, len(filteredInstances)
   137  }
   138  
   139  func (s *TrialCleanupService) cleanupInstances(instances []internal.Instance) (int, int, int) {
   140  	var suspensionAccepted int
   141  	var onlyExpirationMarked int
   142  	totalInstances := len(instances)
   143  	for _, instance := range instances {
   144  		suspensionUnderWay, err := s.expireInstance(instance)
   145  		if err != nil {
   146  			// ignoring errors - only logging
   147  			log.Error(fmt.Sprintf("while sending expiration request for instanceID: %s, error: %s", instance.InstanceID, err))
   148  			continue
   149  		}
   150  		if suspensionUnderWay {
   151  			suspensionAccepted += 1
   152  		} else {
   153  			onlyExpirationMarked += 1
   154  		}
   155  	}
   156  	failures := totalInstances - suspensionAccepted - onlyExpirationMarked
   157  	return suspensionAccepted, onlyExpirationMarked, failures
   158  }
   159  
   160  func (s *TrialCleanupService) logInstances(instances []internal.Instance) {
   161  	for _, instance := range instances {
   162  		log.Infof("instanceId: %+v createdAt: %+v (%.0f days ago) servicePlanID: %+v servicePlanName: %+v",
   163  			instance.InstanceID, instance.CreatedAt, time.Since(instance.CreatedAt).Hours()/24, instance.ServicePlanID, instance.ServicePlanName)
   164  	}
   165  }
   166  
   167  func (s *TrialCleanupService) expireInstance(instance internal.Instance) (processed bool, err error) {
   168  	log.Infof("About to make instance expired for instanceID: %+v", instance.InstanceID)
   169  	suspensionUnderWay, err := s.brokerClient.SendExpirationRequest(instance)
   170  	if err != nil {
   171  		log.Error(fmt.Sprintf("while sending expiration request for instanceID %q: %s", instance.InstanceID, err))
   172  		return suspensionUnderWay, err
   173  	}
   174  	return suspensionUnderWay, nil
   175  }
   176  
   177  func fatalOnError(err error) {
   178  	if err != nil {
   179  		// temporarily we exit with 0 to avoid any side effects - we ignore all errors only logging those
   180  		//log.Fatal(err)
   181  		log.Error(err)
   182  		os.Exit(0)
   183  	}
   184  }