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), ¶ms) 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), ¶ms) 343 if err != nil { 344 return internal.Instance{}, fmt.Errorf("while unmarshal parameters: %w", err) 345 } 346 err = s.cipher.DecryptSMCreds(¶ms) 347 if err != nil { 348 return internal.Instance{}, fmt.Errorf("while decrypting parameters: %w", err) 349 } 350 351 err = s.cipher.DecryptKubeconfig(¶ms) 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 }