Skip to content

The lease protocol cannot express a product family, which Metalama needs #8

Description

The problem

The product argument of Lease.ashx names one licensing product, and LeaseService.GetLicenseLeaseAsync uses it as a filter:

// A client that names no product is served from any pool, which is what every PostSharp
// client relies on: none of them sent the argument.
IReadOnlyList<string> productCodes = string.IsNullOrEmpty( productCode )
    ? []
    : ProductCodes.Matching( productCode );

License[] licenses = await this.repository.Licenses
    .Where( license => (productCodes.Count == 0 || productCodes.Contains( license.ProductCode ))
                       && license.Priority >= 0 )

That gives a client two choices, and neither is right for Metalama.

Send nothing. The client is served from any pool. This is the legacy behaviour, and it means "any PostSharp license key" — it predates Metalama and no PostSharp client ever sent the argument. A Metalama client that sends nothing can be served a license key of a product that does not license Metalama at all; the client then refuses it (the license key is for X and not for Metalama) although the server held a key that would have worked, because the choice was made on the server, by Priority, without knowing who was asking.

Send one product. The client is served only from that pool. But a client does not know which edition its customer bought — only the server does. A client that sends MetalamaProfessional is refused by a server whose pool holds MetalamaEnterprise, and the customer sees No license with free capacity from a server that is holding exactly the licenses they paid for.

What a client actually needs to ask

A product family, not a product. A Metalama client needs to say "any license key that licenses Metalama", and today the protocol has no way to say it.

The family is not a guess — it is already declared, in ILicenseProductCatalog.IsProductOfFamily. For Metalama (MetalamaLicenseProductCatalog) it is:

MetalamaCommunity, MetalamaProfessional, MetalamaEnterprise, MetalamaUltimate, MetalamaStarter, MetalamaFreeand PostSharpUltimate and PostSharpFramework, because a PostSharp key of those editions licenses Metalama too.

So the set is neither "one product" nor "every product", and it is not derivable from the name by prefix.

Proposal

Let product name a family as well as a product: product=Metalama is served from every license whose ProductCode is a product of the Metalama family, and product=PostSharp likewise.

The server already references SharpCrafters.Backstage.Licensing and already has a catalog (LicenseServerProductCatalog : LicenseProductCatalog), so resolving a family name to its products is a lookup rather than a new concept. ProductCodes.Matching is the natural place: it already maps one name to the several spellings a database may hold, and this widens the same mapping from spellings to a family.

Compatibility is preserved in both directions: a client that names one product keeps being served from that pool alone, and a client that names nothing keeps being served from any, which is what the deployed PostSharp clients rely on.

Until then

The client side is ILicenseProductCatalog.LicenseServerProduct. It is today the evaluation product of the catalog, so a Metalama client sends product=MetalamaProfessional — the second of the two wrongs above, and the one that denies a customer whose pool is MetalamaEnterprise. That has to change to sending nothing before this ships: a customer whose server holds one pool is then always served, and a customer with a mixed server chooses with Priority. The hook stays available for a product that genuinely knows its own pool.

See postsharp-ops/SharpCrafters.Backstage#1 and metalama/Metalama#946 for the client side.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions