Skip to content

feat(kafka create): add billing model logic#1636

Merged
rkpattnaik780 merged 13 commits intoredhat-developer:mainfrom
rkpattnaik780:ams_billing_model
Jul 15, 2022
Merged

feat(kafka create): add billing model logic#1636
rkpattnaik780 merged 13 commits intoredhat-developer:mainfrom
rkpattnaik780:ams_billing_model

Conversation

@rkpattnaik780
Copy link
Contributor

@rkpattnaik780 rkpattnaik780 commented Jul 12, 2022

This PR adds --billing-model flag to rhoas kafka create command.
The CLI should be able to create instances with standard, marketplace and trial quotas.

Verification Steps

  1. For testing against marketplace (use _kafka_service account).

rhoas kafka create --name test-instance should throw error saying multiple quota types are available and user needs to specify billing model.
rhoas kafka create --name test-instance --billing-model standard should create a standard instance
rhoas kafka create --name test-instance --billing-model standard should create a marketplace instance

rhoas kafka create should start the creation in interactive mode and prompt for billing model.

User should be able to pick marketplace provider and market place account if multiple options are available, else the only account gets selected by default.

  1. When only trial instances are available (use _kafka_devexp account)

Specifying the billing model should fail with message "only trial instances available".

rhoas kafka create --name test-instance should create a trial instance.

Interactive mode should not prompt billing model.

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation change
  • Other (please specify)


if opts.bypassChecks && (opts.marketplace != "" || opts.marketplaceAcctId != "") {
if opts.bypassChecks && (opts.marketplace != "" || opts.marketplaceAcctId != "" || opts.billingModel != "") {
return f.Localizer.MustLocalizeError("kafka.create.error.bypassChecks.marketplace")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Description needs billing model flag

return f.Localizer.MustLocalizeError("kafka.create.error.bypassChecks.marketplace")
}

if opts.billingModel == "standard" && (opts.marketplaceAcctId != "" || opts.marketplace != "") {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest to not list any billing model in create.go. Also not hardcoding it's value.

return GetCloudProviderRegionCompletionValues(f, opts.provider)
})

_ = cmd.RegisterFlagCompletionFunc(FlagSize, func(cmd *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why removing those?

}

userInstanceType, err = accountmgmtutil.GetUserSupportedInstanceType(f.Context, &constants.Kafka.Ams, conn)
marketplaceInfo := accountmgmtutil.MarketplaceInfo{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be transformed as inline structure passed to method.

userInstanceType, err = accountmgmtutil.GetUserSupportedInstanceType(f, &constants.Kafka.Ams, marketplaceInfo)
if err != nil || userInstanceType == nil {
return f.Localizer.MustLocalizeError("kafka.create.error.userInstanceType.notFound")
return err // || f.Localizer.MustLocalizeError("kafka.create.error.userInstanceType.notFound")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

userInstanceType == nil will print Error: nil to user


}

if userInstanceType.BillingModel == "standard" || userInstanceType.BillingModel == "marketplace" {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are no other billing models and if they would be in future we should still pass them. I think this is redundant.

}
}

marketplaces, err := accountmgmtutil.GetValidMarketplaces(f.Context, f.Connection)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why removing this?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we want to ask user to pick marketplace details this should happen before size and feed back into size (this is current bug in relased version of the CLI). I do not see how interactive mode would work with marketplace right now. Would we fail/still require users to use flags for non interactive selects?

} else if quotaResource.GetProduct() == spec.InstanceQuotaID {
remainingQuota := int(quota.GetAllowed() - quota.GetConsumed())
quotas = append(quotas, QuotaSpec{QuotaStandardType, remainingQuota, quotaResource.BillingModel})
if quotaResource.BillingModel == "standard" {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems redundant - standard will still have cloudAccounts with nil value.
It is never a bad idea to debug your code for understanding.

}

return amsType, nil
type OrgQuotas struct {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While this is good I would myself keep all Quota spec as array and use filter flags (billingModel) to filter them out.

There is big decision that we can take:

  • Have trial as quota object (it will be always there) thus if logic is `len(quotas) == 1 { return quotas[0] }
  • Ignore trial and do not return quota for it (it will be more accurate from business logic point of view , but it will require rewrite of the create.go logic.


func SelectQuotaForUser(f *factory.Factory, orgQuota *OrgQuotas, marketplaceInfo MarketplaceInfo) (*QuotaSpec, error) {
if len(orgQuota.StandardQuotas) == 0 && len(orgQuota.MarketplaceQuotas) == 0 {
if len(orgQuota.TrialQuotas) == 0 {
Copy link
Collaborator

@wtrocki wtrocki Jul 12, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trial would be always present. Also doesn't need to checked in quota selection.

Decision we need to take here is:

  • do we want to fail when user specify rubbish billing model or accoutID that doesn't exist?
  • Can we build an decision graph for selection filtering that will be future proof?
    I have build diagram to showcase how this would look like for current model

Screenshot 2022-07-12 at 11 31 51

@rkpattnaik780 rkpattnaik780 requested a review from wtrocki July 13, 2022 13:27
@wtrocki wtrocki requested a review from jackdelahunt July 13, 2022 15:17
@wtrocki
Copy link
Collaborator

wtrocki commented Jul 13, 2022

@rkpattnaik780 We are missing verification steps for @jackdelahunt

}

// GetMarketplaceAcctIdCompletionValues returns a list of valid marketplace account IDs for the organization
func GetMarketplaceAcctIdCompletionValues(f *factory.Factory) (validMarketplaceAcctIDs []string, directive cobra.ShellCompDirective) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reason to remove this?

validMarketPlaces := FetchValidMarketplaces(orgQuota.MarketplaceQuotas)
if len(validMarketPlaces) == 1 {
answers.Marketplace = validMarketPlaces[0]
} else {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How this would work with len(validMarketPlaces) == 0?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ignore me. That would not be possible

Comment on lines +199 to +203
if marketplace.Provider != "" && marketplace.CloudAccountID != "" {
if *cloudAccount.CloudProviderId == marketplace.Provider && *cloudAccount.CloudAccountId == marketplace.CloudAccountID {
matchedQuotas = append(matchedQuotas, quota)
}
} else if marketplace.Provider != "" || marketplace.CloudAccountID != "" {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Arent those the same if checks?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was to handle the case of providing only provider or account ID.

}

func GetValidMarketplaces(ctx context.Context, connectionFunc factory.ConnectionFunc) (marketplaces []string, err error) {
func pickCloudAccount(f *factory.Factory, cloudAccounts *[]amsclient.CloudAccount, market MarketplaceInfo) (*[]amsclient.CloudAccount, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this method return single object instead of array. It does pick cloud account right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Used array to make it easy to use with quotapsec object.

var matchedAccounts []amsclient.CloudAccount

for _, cloudAccount := range *cloudAccounts {
if market.Provider != "" || market.CloudAccountID != "" {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those variables are set outside for so we might do them once outside loop?


for _, cloudAccount := range *cloudAccounts {
if market.Provider != "" || market.CloudAccountID != "" {
if *cloudAccount.CloudProviderId == market.Provider || *cloudAccount.CloudAccountId == market.CloudAccountID {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question I have:
Do you think we should support people specifying only market.Provider?

Maybe we can just:

Suggested change
if *cloudAccount.CloudProviderId == market.Provider || *cloudAccount.CloudAccountId == market.CloudAccountID {
if *cloudAccount.CloudProviderId == market.Provider && *cloudAccount.CloudAccountId == market.CloudAccountID {

To keep this convoluted logic little bit simpler?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think we should support people specifying only market.Provider?

I will agree not allowing marketplace provider alone can make the logic bit simpler. I will be removing it.

@rkpattnaik780 rkpattnaik780 requested a review from wtrocki July 14, 2022 11:15
@rkpattnaik780 rkpattnaik780 changed the title wip: feat(kafka create): add billing model logic feat(kafka create): add billing model logic Jul 14, 2022
@rkpattnaik780 rkpattnaik780 marked this pull request as ready for review July 14, 2022 11:15
@wtrocki
Copy link
Collaborator

wtrocki commented Jul 14, 2022

@rkpattnaik780 let's do end to end verification for this together.

@jackdelahunt
Copy link
Contributor

jackdelahunt commented Jul 14, 2022

Ran into an issue.

When only trial instances were available setting the billing model (standard or marketplace) did not throw an error as described in the verification steps.

@rkpattnaik780
Copy link
Contributor Author

Ran into an issue.

When only trial instances were available setting the billing model (standard or marketplace) did not throw an error as described in the verification steps.

Hi @jackdelahunt
Not able to reproduce it. Can you paste the command in verbose mode.

@jackdelahunt
Copy link
Contributor

@rkpattnaik780
Copy link
Contributor Author

Here -> https://gist.github.com/jackdelahunt/81e45bfcbee10b38f9bdaab3e35800ca

Thanks for the catch, PR has been updated.

@jackdelahunt
Copy link
Contributor

From what I can do (step 2) LGTM 👍

@wtrocki
Copy link
Collaborator

wtrocki commented Jul 14, 2022

I have tried to do "Duncan Test" as start:

[wtrocki@graphapi app-services-cli (pr/rkpattnaik780/1636 ✗)]$ rhoas kafka create                   
? Name: duncan-test
? Cloud Provider: aws
? Billing Model: marketplace
? Cloud Region: us-east-1
Kafka instance with size 1 is being created. (Size id: x1)
❌ 400 Bad Request. Run the command in verbose mode using the -v flag to see more information

It has:

  • Service API user (Enables marketplace, but no standard) - standard was still present
  • Wrong plan selected (developer plan/trial was on where I selected proper billing model (marketplace)
  • Sizes provided were wrong only x1 (my plan had support up to x2)

Error detail:

{"reason":"Unable to detect instance type in plan provided: 'developer.x1'","operation_id":"cb82cemo4k31f4mqqo9g","id":"21","kind":"Error","href":"/api/kafkas_mgmt/v1/errors/21","code":"KAFKAS-MGMT-21"}

@rkpattnaik780 rkpattnaik780 merged commit fb51888 into redhat-developer:main Jul 15, 2022
@wtrocki
Copy link
Collaborator

wtrocki commented Jul 15, 2022

@rkpattnaik780 let's do more comprehensive testing with control plane team and mock. Once we have confidence in this we can release.

f.Logger.Debug(fmt.Sprintf("Selected quota object: %#v", userQuota))
userQuotaJSON, _ := json.MarshalIndent(userQuota, "", " ")
f.Logger.Debug("Selected Quota object:")
f.Logger.Debug(string(userQuotaJSON))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's create helper for logging that type of stuff. It takes a lot of space.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants