github.com/kyma-project/kyma-environment-broker@v0.0.1/internal/storage/driver/postsql/instance.go (about)

     1  package postsql
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"strings"
     7  
     8  	"github.com/pivotal-cf/brokerapi/v8/domain"
     9  
    10  	"github.com/kyma-project/kyma-environment-broker/internal"
    11  	"github.com/kyma-project/kyma-environment-broker/internal/storage/dberr"
    12  	"github.com/kyma-project/kyma-environment-broker/internal/storage/dbmodel"
    13  	"github.com/kyma-project/kyma-environment-broker/internal/storage/postsql"
    14  	"github.com/kyma-project/kyma-environment-broker/internal/storage/predicate"
    15  	log "github.com/sirupsen/logrus"
    16  	"k8s.io/apimachinery/pkg/util/wait"
    17  )
    18  
    19  type Instance struct {
    20  	postsql.Factory
    21  	operations *operations
    22  	cipher     Cipher
    23  }
    24  
    25  func NewInstance(sess postsql.Factory, operations *operations, cipher Cipher) *Instance {
    26  	return &Instance{
    27  		Factory:    sess,
    28  		operations: operations,
    29  		cipher:     cipher,
    30  	}
    31  }
    32  
    33  func (s *Instance) InsertWithoutEncryption(instance internal.Instance) error {
    34  	_, err := s.GetByID(instance.InstanceID)
    35  	if err == nil {
    36  		return dberr.AlreadyExists("instance with id %s already exist", instance.InstanceID)
    37  	}
    38  	params, err := json.Marshal(instance.Parameters)
    39  	if err != nil {
    40  		return fmt.Errorf("while marshaling parameters: %w", err)
    41  	}
    42  	dto := dbmodel.InstanceDTO{
    43  		InstanceID:             instance.InstanceID,
    44  		RuntimeID:              instance.RuntimeID,
    45  		GlobalAccountID:        instance.GlobalAccountID,
    46  		SubAccountID:           instance.SubAccountID,
    47  		ServiceID:              instance.ServiceID,
    48  		ServiceName:            instance.ServiceName,
    49  		ServicePlanID:          instance.ServicePlanID,
    50  		ServicePlanName:        instance.ServicePlanName,
    51  		DashboardURL:           instance.DashboardURL,
    52  		ProvisioningParameters: string(params),
    53  		ProviderRegion:         instance.ProviderRegion,
    54  		CreatedAt:              instance.CreatedAt,
    55  		UpdatedAt:              instance.UpdatedAt,
    56  		DeletedAt:              instance.DeletedAt,
    57  		Version:                instance.Version,
    58  		Provider:               string(instance.Provider),
    59  	}
    60  
    61  	sess := s.NewWriteSession()
    62  	return wait.PollImmediate(defaultRetryInterval, defaultRetryTimeout, func() (bool, error) {
    63  		err := sess.InsertInstance(dto)
    64  		if err != nil {
    65  			log.Errorf("while saving instance ID %s: %v", instance.InstanceID, err)
    66  			return false, nil
    67  		}
    68  		return true, nil
    69  	})
    70  }
    71  
    72  func (s *Instance) ListWithoutDecryption(filter dbmodel.InstanceFilter) ([]internal.Instance, int, int, error) {
    73  	dtos, count, totalCount, err := s.NewReadSession().ListInstances(filter)
    74  	if err != nil {
    75  		return []internal.Instance{}, 0, 0, err
    76  	}
    77  	var instances []internal.Instance
    78  	for _, dto := range dtos {
    79  		var params internal.ProvisioningParameters
    80  		err := json.Unmarshal([]byte(dto.ProvisioningParameters), &params)
    81  		if err != nil {
    82  			return nil, 0, 0, fmt.Errorf("while unmarshal parameters: %w", err)
    83  		}
    84  		instance := internal.Instance{
    85  			InstanceID:      dto.InstanceID,
    86  			RuntimeID:       dto.RuntimeID,
    87  			GlobalAccountID: dto.GlobalAccountID,
    88  			SubAccountID:    dto.SubAccountID,
    89  			ServiceID:       dto.ServiceID,
    90  			ServiceName:     dto.ServiceName,
    91  			ServicePlanID:   dto.ServicePlanID,
    92  			ServicePlanName: dto.ServicePlanName,
    93  			DashboardURL:    dto.DashboardURL,
    94  			Parameters:      params,
    95  			ProviderRegion:  dto.ProviderRegion,
    96  			CreatedAt:       dto.CreatedAt,
    97  			UpdatedAt:       dto.UpdatedAt,
    98  			DeletedAt:       dto.DeletedAt,
    99  			Version:         dto.Version,
   100  			Provider:        internal.CloudProvider(dto.Provider),
   101  		}
   102  		instances = append(instances, instance)
   103  	}
   104  	return instances, count, totalCount, err
   105  }
   106  
   107  func (s *Instance) UpdateWithoutEncryption(instance internal.Instance) (*internal.Instance, error) {
   108  	sess := s.NewWriteSession()
   109  	params, err := json.Marshal(instance.Parameters)
   110  	if err != nil {
   111  		return nil, fmt.Errorf("while marshaling parameters: %w", err)
   112  	}
   113  	dto := dbmodel.InstanceDTO{
   114  		InstanceID:             instance.InstanceID,
   115  		RuntimeID:              instance.RuntimeID,
   116  		GlobalAccountID:        instance.GlobalAccountID,
   117  		SubAccountID:           instance.SubAccountID,
   118  		ServiceID:              instance.ServiceID,
   119  		ServiceName:            instance.ServiceName,
   120  		ServicePlanID:          instance.ServicePlanID,
   121  		ServicePlanName:        instance.ServicePlanName,
   122  		DashboardURL:           instance.DashboardURL,
   123  		ProvisioningParameters: string(params),
   124  		ProviderRegion:         instance.ProviderRegion,
   125  		CreatedAt:              instance.CreatedAt,
   126  		UpdatedAt:              instance.UpdatedAt,
   127  		DeletedAt:              instance.DeletedAt,
   128  		Version:                instance.Version,
   129  		Provider:               string(instance.Provider),
   130  	}
   131  	var lastErr dberr.Error
   132  	err = wait.PollImmediate(defaultRetryInterval, defaultRetryTimeout, func() (bool, error) {
   133  		lastErr = sess.UpdateInstance(dto)
   134  
   135  		switch {
   136  		case dberr.IsNotFound(lastErr):
   137  			_, lastErr = s.NewReadSession().GetInstanceByID(instance.InstanceID)
   138  			if dberr.IsNotFound(lastErr) {
   139  				return false, dberr.NotFound("Instance with id %s not exist", instance.InstanceID)
   140  			}
   141  			if lastErr != nil {
   142  				log.Errorf(fmt.Sprintf("while getting Operation: %v", lastErr))
   143  				return false, nil
   144  			}
   145  
   146  			// the operation exists but the version is different
   147  			lastErr = dberr.Conflict("operation update conflict, operation ID: %s", instance.InstanceID)
   148  			return false, lastErr
   149  		case lastErr != nil:
   150  			log.Errorf("while updating instance ID %s: %v", instance.InstanceID, lastErr)
   151  			return false, nil
   152  		}
   153  		return true, nil
   154  	})
   155  	if err != nil {
   156  		return nil, lastErr
   157  	}
   158  	instance.Version = instance.Version + 1
   159  	return &instance, nil
   160  }
   161  
   162  func (s *Instance) FindAllJoinedWithOperations(prct ...predicate.Predicate) ([]internal.InstanceWithOperation, error) {
   163  	sess := s.NewReadSession()
   164  	var (
   165  		instances []dbmodel.InstanceWithOperationDTO
   166  		lastErr   dberr.Error
   167  	)
   168  	err := wait.PollImmediate(defaultRetryInterval, defaultRetryTimeout, func() (bool, error) {
   169  		instances, lastErr = sess.FindAllInstancesJoinedWithOperation(prct...)
   170  		if lastErr != nil {
   171  			log.Errorf("while fetching all instances: %v", lastErr)
   172  			return false, nil
   173  		}
   174  		return true, nil
   175  	})
   176  	if err != nil {
   177  		return nil, lastErr
   178  	}
   179  
   180  	var result []internal.InstanceWithOperation
   181  	for _, dto := range instances {
   182  		inst, err := s.toInstance(dto.InstanceDTO)
   183  		if err != nil {
   184  			return nil, err
   185  		}
   186  
   187  		var isSuspensionOp bool
   188  
   189  		switch internal.OperationType(dto.Type.String) {
   190  		case internal.OperationTypeProvision:
   191  			isSuspensionOp = false
   192  		case internal.OperationTypeDeprovision:
   193  			deprovOp, err := s.toDeprovisioningOp(&dto)
   194  			if err != nil {
   195  				log.Errorf("while unmarshalling DTO deprovisioning operation data: %v", err)
   196  			}
   197  			isSuspensionOp = deprovOp.Temporary
   198  		}
   199  
   200  		result = append(result, internal.InstanceWithOperation{
   201  			Instance:       inst,
   202  			Type:           dto.Type,
   203  			State:          dto.State,
   204  			Description:    dto.Description,
   205  			OpCreatedAt:    dto.OperationCreatedAt.Time,
   206  			IsSuspensionOp: isSuspensionOp,
   207  		})
   208  	}
   209  
   210  	return result, nil
   211  }
   212  
   213  func (s *Instance) toProvisioningOp(dto *dbmodel.InstanceWithOperationDTO) (*internal.ProvisioningOperation, error) {
   214  	var provOp internal.ProvisioningOperation
   215  	err := json.Unmarshal([]byte(dto.Data.String), &provOp)
   216  	if err != nil {
   217  		return nil, fmt.Errorf("unable to unmarshall provisioning data")
   218  	}
   219  
   220  	return &provOp, nil
   221  }
   222  
   223  func (s *Instance) toDeprovisioningOp(dto *dbmodel.InstanceWithOperationDTO) (*internal.DeprovisioningOperation, error) {
   224  	var deprovOp internal.DeprovisioningOperation
   225  	err := json.Unmarshal([]byte(dto.Data.String), &deprovOp)
   226  	if err != nil {
   227  		return nil, fmt.Errorf("unable to unmarshall deprovisioning data")
   228  	}
   229  
   230  	return &deprovOp, nil
   231  }
   232  
   233  func (s *Instance) FindAllInstancesForRuntimes(runtimeIdList []string) ([]internal.Instance, error) {
   234  	sess := s.NewReadSession()
   235  	var instances []dbmodel.InstanceDTO
   236  	var lastErr dberr.Error
   237  	err := wait.PollImmediate(defaultRetryInterval, defaultRetryTimeout, func() (bool, error) {
   238  		instances, lastErr = sess.FindAllInstancesForRuntimes(runtimeIdList)
   239  		if lastErr != nil {
   240  			if dberr.IsNotFound(lastErr) {
   241  				return false, dberr.NotFound("Instances with runtime IDs from list '%+q' not exist", runtimeIdList)
   242  			}
   243  			log.Errorf("while getting instances from runtime ID list '%+q': %v", runtimeIdList, lastErr)
   244  			return false, nil
   245  		}
   246  		return true, nil
   247  	})
   248  	if err != nil {
   249  		return nil, lastErr
   250  	}
   251  
   252  	var result []internal.Instance
   253  	for _, dto := range instances {
   254  		inst, err := s.toInstance(dto)
   255  		if err != nil {
   256  			return []internal.Instance{}, err
   257  		}
   258  		result = append(result, inst)
   259  	}
   260  
   261  	return result, nil
   262  }
   263  
   264  func (s *Instance) FindAllInstancesForSubAccounts(subAccountslist []string) ([]internal.Instance, error) {
   265  	sess := s.NewReadSession()
   266  	var (
   267  		instances []dbmodel.InstanceDTO
   268  		lastErr   dberr.Error
   269  	)
   270  	err := wait.PollImmediate(defaultRetryInterval, defaultRetryTimeout, func() (bool, error) {
   271  		instances, lastErr = sess.FindAllInstancesForSubAccounts(subAccountslist)
   272  		if lastErr != nil {
   273  			log.Errorf("while fetching instances by subaccount list: %v", lastErr)
   274  			return false, nil
   275  		}
   276  		return true, nil
   277  	})
   278  	if err != nil {
   279  		return nil, lastErr
   280  	}
   281  
   282  	var result []internal.Instance
   283  	for _, dto := range instances {
   284  		inst, err := s.toInstance(dto)
   285  		if err != nil {
   286  			return []internal.Instance{}, err
   287  		}
   288  		result = append(result, inst)
   289  	}
   290  
   291  	return result, nil
   292  }
   293  
   294  func (s *Instance) GetNumberOfInstancesForGlobalAccountID(globalAccountID string) (int, error) {
   295  	sess := s.NewReadSession()
   296  	var result int
   297  	err := wait.PollImmediate(defaultRetryInterval, defaultRetryTimeout, func() (bool, error) {
   298  		count, err := sess.GetNumberOfInstancesForGlobalAccountID(globalAccountID)
   299  		result = count
   300  		return err == nil, nil
   301  	})
   302  	return result, err
   303  }
   304  
   305  // TODO: Wrap retries in single method WithRetries
   306  func (s *Instance) GetByID(instanceID string) (*internal.Instance, error) {
   307  	sess := s.NewReadSession()
   308  	instanceDTO := dbmodel.InstanceDTO{}
   309  	var lastErr dberr.Error
   310  	err := wait.PollImmediate(defaultRetryInterval, defaultRetryTimeout, func() (bool, error) {
   311  		instanceDTO, lastErr = sess.GetInstanceByID(instanceID)
   312  		if lastErr != nil {
   313  			if dberr.IsNotFound(lastErr) {
   314  				return false, dberr.NotFound("Instance with id %s not exist", instanceID)
   315  			}
   316  			log.Errorf("while getting instanceDTO by ID %s: %v", instanceID, lastErr)
   317  			return false, nil
   318  		}
   319  		return true, nil
   320  	})
   321  	if err != nil {
   322  		return nil, lastErr
   323  	}
   324  	instance, err := s.toInstance(instanceDTO)
   325  	if err != nil {
   326  		return nil, err
   327  	}
   328  
   329  	lastOp, err := s.operations.GetLastOperation(instanceID)
   330  	if err != nil {
   331  		if dberr.IsNotFound(err) {
   332  			return &instance, nil
   333  		}
   334  		return nil, err
   335  	}
   336  	instance.InstanceDetails = lastOp.InstanceDetails
   337  	return &instance, nil
   338  }
   339  
   340  func (s *Instance) toInstance(dto dbmodel.InstanceDTO) (internal.Instance, error) {
   341  	var params internal.ProvisioningParameters
   342  	err := json.Unmarshal([]byte(dto.ProvisioningParameters), &params)
   343  	if err != nil {
   344  		return internal.Instance{}, fmt.Errorf("while unmarshal parameters: %w", err)
   345  	}
   346  	err = s.cipher.DecryptSMCreds(&params)
   347  	if err != nil {
   348  		return internal.Instance{}, fmt.Errorf("while decrypting parameters: %w", err)
   349  	}
   350  
   351  	err = s.cipher.DecryptKubeconfig(&params)
   352  	if err != nil {
   353  		log.Warn("decrypting skipped because kubeconfig is in a plain text")
   354  	}
   355  
   356  	return internal.Instance{
   357  		InstanceID:                  dto.InstanceID,
   358  		RuntimeID:                   dto.RuntimeID,
   359  		GlobalAccountID:             dto.GlobalAccountID,
   360  		SubscriptionGlobalAccountID: dto.SubscriptionGlobalAccountID,
   361  		SubAccountID:                dto.SubAccountID,
   362  		ServiceID:                   dto.ServiceID,
   363  		ServiceName:                 dto.ServiceName,
   364  		ServicePlanID:               dto.ServicePlanID,
   365  		ServicePlanName:             dto.ServicePlanName,
   366  		DashboardURL:                dto.DashboardURL,
   367  		Parameters:                  params,
   368  		ProviderRegion:              dto.ProviderRegion,
   369  		CreatedAt:                   dto.CreatedAt,
   370  		UpdatedAt:                   dto.UpdatedAt,
   371  		DeletedAt:                   dto.DeletedAt,
   372  		ExpiredAt:                   dto.ExpiredAt,
   373  		Version:                     dto.Version,
   374  		Provider:                    internal.CloudProvider(dto.Provider),
   375  	}, nil
   376  }
   377  
   378  func (s *Instance) Insert(instance internal.Instance) error {
   379  	_, err := s.GetByID(instance.InstanceID)
   380  	if err == nil {
   381  		return dberr.AlreadyExists("instance with id %s already exist", instance.InstanceID)
   382  	}
   383  
   384  	dto, err := s.toInstanceDTO(instance)
   385  	if err != nil {
   386  		return err
   387  	}
   388  
   389  	sess := s.NewWriteSession()
   390  	return wait.PollImmediate(defaultRetryInterval, defaultRetryTimeout, func() (bool, error) {
   391  		err := sess.InsertInstance(dto)
   392  		if err != nil {
   393  			log.Errorf("while saving instance ID %s: %v", instance.InstanceID, err)
   394  			return false, nil
   395  		}
   396  		return true, nil
   397  	})
   398  }
   399  
   400  func (s *Instance) Update(instance internal.Instance) (*internal.Instance, error) {
   401  	sess := s.NewWriteSession()
   402  	dto, err := s.toInstanceDTO(instance)
   403  	if err != nil {
   404  		return nil, err
   405  	}
   406  	var lastErr dberr.Error
   407  	err = wait.PollImmediate(defaultRetryInterval, defaultRetryTimeout, func() (bool, error) {
   408  		lastErr = sess.UpdateInstance(dto)
   409  
   410  		switch {
   411  		case dberr.IsNotFound(lastErr):
   412  			_, lastErr = s.NewReadSession().GetInstanceByID(instance.InstanceID)
   413  			if dberr.IsNotFound(lastErr) {
   414  				return false, dberr.NotFound("Instance with id %s not exist", instance.InstanceID)
   415  			}
   416  			if lastErr != nil {
   417  				log.Warn(fmt.Errorf("while getting Operation: %w", lastErr))
   418  				return false, nil
   419  			}
   420  
   421  			// the operation exists but the version is different
   422  			lastErr = dberr.Conflict("operation update conflict, operation ID: %s", instance.InstanceID)
   423  			return false, lastErr
   424  		case lastErr != nil:
   425  			log.Errorf("while updating instance ID %s: %v", instance.InstanceID, lastErr)
   426  			return false, nil
   427  		}
   428  		return true, nil
   429  	})
   430  	if err != nil {
   431  		return nil, lastErr
   432  	}
   433  	instance.Version = instance.Version + 1
   434  	return &instance, nil
   435  }
   436  
   437  func (s *Instance) toInstanceDTO(instance internal.Instance) (dbmodel.InstanceDTO, error) {
   438  	err := s.cipher.EncryptSMCreds(&instance.Parameters)
   439  	if err != nil {
   440  		return dbmodel.InstanceDTO{}, fmt.Errorf("while encrypting parameters: %w", err)
   441  	}
   442  	err = s.cipher.EncryptKubeconfig(&instance.Parameters)
   443  	if err != nil {
   444  		return dbmodel.InstanceDTO{}, fmt.Errorf("while encrypting kubeconfig: %w", err)
   445  	}
   446  	params, err := json.Marshal(instance.Parameters)
   447  	if err != nil {
   448  		return dbmodel.InstanceDTO{}, fmt.Errorf("while marshaling parameters: %w", err)
   449  	}
   450  	return dbmodel.InstanceDTO{
   451  		InstanceID:                  instance.InstanceID,
   452  		RuntimeID:                   instance.RuntimeID,
   453  		GlobalAccountID:             instance.GlobalAccountID,
   454  		SubscriptionGlobalAccountID: instance.SubscriptionGlobalAccountID,
   455  		SubAccountID:                instance.SubAccountID,
   456  		ServiceID:                   instance.ServiceID,
   457  		ServiceName:                 instance.ServiceName,
   458  		ServicePlanID:               instance.ServicePlanID,
   459  		ServicePlanName:             instance.ServicePlanName,
   460  		DashboardURL:                instance.DashboardURL,
   461  		ProvisioningParameters:      string(params),
   462  		ProviderRegion:              instance.ProviderRegion,
   463  		CreatedAt:                   instance.CreatedAt,
   464  		UpdatedAt:                   instance.UpdatedAt,
   465  		DeletedAt:                   instance.DeletedAt,
   466  		ExpiredAt:                   instance.ExpiredAt,
   467  		Version:                     instance.Version,
   468  		Provider:                    string(instance.Provider),
   469  	}, nil
   470  }
   471  
   472  func (s *Instance) Delete(instanceID string) error {
   473  	sess := s.NewWriteSession()
   474  	return sess.DeleteInstance(instanceID)
   475  }
   476  
   477  func (s *Instance) GetInstanceStats() (internal.InstanceStats, error) {
   478  	entries, err := s.NewReadSession().GetInstanceStats()
   479  	if err != nil {
   480  		return internal.InstanceStats{}, err
   481  	}
   482  
   483  	result := internal.InstanceStats{
   484  		PerGlobalAccountID: make(map[string]int),
   485  	}
   486  	for _, e := range entries {
   487  		result.PerGlobalAccountID[e.GlobalAccountID] = e.Total
   488  		result.TotalNumberOfInstances = result.TotalNumberOfInstances + e.Total
   489  	}
   490  	return result, nil
   491  }
   492  
   493  func (s *Instance) GetERSContextStats() (internal.ERSContextStats, error) {
   494  	entries, err := s.NewReadSession().GetERSContextStats()
   495  	if err != nil {
   496  		return internal.ERSContextStats{}, err
   497  	}
   498  	result := internal.ERSContextStats{
   499  		LicenseType: make(map[string]int),
   500  	}
   501  	for _, e := range entries {
   502  		result.LicenseType[strings.Trim(e.LicenseType.String, `"`)] += e.Total
   503  	}
   504  	return result, nil
   505  }
   506  
   507  func (s *Instance) List(filter dbmodel.InstanceFilter) ([]internal.Instance, int, int, error) {
   508  	dtos, count, totalCount, err := s.NewReadSession().ListInstances(filter)
   509  	if err != nil {
   510  		return []internal.Instance{}, 0, 0, err
   511  	}
   512  	var instances []internal.Instance
   513  	for _, dto := range dtos {
   514  		instance, err := s.toInstance(dto)
   515  		if err != nil {
   516  			return []internal.Instance{}, 0, 0, err
   517  		}
   518  		lastOp, err := s.operations.GetLastOperation(instance.InstanceID)
   519  		if err != nil {
   520  			if dberr.IsNotFound(err) {
   521  				instances = append(instances, instance)
   522  				continue
   523  			}
   524  			return []internal.Instance{}, 0, 0, err
   525  		}
   526  		instance.InstanceDetails = lastOp.InstanceDetails
   527  		instance.Reconcilable = instance.RuntimeID != "" && lastOp.Type != internal.OperationTypeDeprovision && lastOp.State != domain.InProgress
   528  		instances = append(instances, instance)
   529  	}
   530  	return instances, count, totalCount, err
   531  }