From fd88ce5890ee8c566852cc1d20dcaff40b1b285c Mon Sep 17 00:00:00 2001 From: Nikolay Edigaryev Date: Wed, 26 Jul 2023 17:28:38 +0400 Subject: [PATCH] Introduce ORCHARD_LICENSE_TIER environment variable (#111) * Introduce ORCHARD_LICENSE_TIER environment variable * Only parse ORCHARD_LICENSE_TIER if it was provided --- README.md | 13 +++++++------ internal/controller/api_workers.go | 13 +++++++++++++ internal/controller/controller.go | 23 +++++++++++++++++++++++ 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 3b238ac..76486d5 100644 --- a/README.md +++ b/README.md @@ -71,9 +71,10 @@ Orchard Controller instance is secured by default and all API calls are authenti In addition to controlling the Orchard via the CLI arguments, there are environment variables that may be beneficial both when automating Orchard and in daily use: -| Variable name | Description | -|---------------------------------|----------------------------------------------------------------------------------------------------------------------| -| `ORCHARD_HOME` | Override Orchard's home directory. Useful when running multiple Orchard instances on the same host and when testing. | -| `ORCHARD_SERVICE_ACCOUNT_NAME` | Override service account name (used for controller API auth) on per-command basis | -| `ORCHARD_SERVICE_ACCOUNT_TOKEN` | Override service account token (used for controller API auth) on per-command basis | -| `ORCHARD_URL` | Override controller URL on per-command basis | +| Variable name | Description | +|---------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `ORCHARD_HOME` | Override Orchard's home directory. Useful when running multiple Orchard instances on the same host and when testing. | +| `ORCHARD_LICENSE_TIER` | The default license limit only allows connecting 4 Orchard Workers to the Orchard Controller. If you've purchased a [Gold Tier License](https://tart.run/licensing/), set this variable to `gold` to increase the limit to 20 Orchard Workers. And if you've purchased a [Platinum Tier License](https://tart.run/licensing/), set this variable to `platinum` to increase the limit to 200 Orchard Workers. | +| `ORCHARD_SERVICE_ACCOUNT_NAME` | Override service account name (used for controller API auth) on per-command basis | +| `ORCHARD_SERVICE_ACCOUNT_TOKEN` | Override service account token (used for controller API auth) on per-command basis | +| `ORCHARD_URL` | Override controller URL on per-command basis | diff --git a/internal/controller/api_workers.go b/internal/controller/api_workers.go index b20b227..163d990 100644 --- a/internal/controller/api_workers.go +++ b/internal/controller/api_workers.go @@ -44,6 +44,19 @@ func (controller *Controller) createWorker(ctx *gin.Context) responder.Responder NewErrorResponse("this worker is managed from a different machine ID, "+ "delete this worker first to be able to re-create it")) } + if errors.Is(err, storepkg.ErrNotFound) { + // We will be adding a new worker, check if the license capacity allows that + workers, err := txn.ListWorkers() + if err != nil { + return responder.Code(http.StatusInternalServerError) + } + + if uint(len(workers)+1) > controller.maxWorkersPerLicense { + return responder.JSON(http.StatusConflict, NewErrorResponse("cannot register a new worker "+ + "because the license capacity of %d workers has been reached, "+ + "consider upgrading at https://tart.run/licensing/", controller.maxWorkersPerLicense)) + } + } if err := txn.SetWorker(worker); err != nil { return responder.Error(err) diff --git a/internal/controller/controller.go b/internal/controller/controller.go index f7f4cd6..433463c 100644 --- a/internal/controller/controller.go +++ b/internal/controller/controller.go @@ -20,6 +20,7 @@ import ( "google.golang.org/grpc/keepalive" "net" "net/http" + "os" "strings" "time" ) @@ -29,6 +30,12 @@ var ( ErrAdminTaskFailed = errors.New("controller administrative task failed") ) +const ( + maxWorkersPerDefaultLicense = 4 + maxWorkersPerGoldLicense = 20 + maxWorkersPerPlatinumLicense = 200 +) + type Controller struct { dataDir *DataDir listenAddr string @@ -44,6 +51,7 @@ type Controller struct { proxy *proxy.Proxy enableSwaggerDocs bool workerOfflineTimeout time.Duration + maxWorkersPerLicense uint rpc.UnimplementedControllerServer } @@ -53,6 +61,7 @@ func New(opts ...Option) (*Controller, error) { workerNotifier: notifier.NewNotifier(), proxy: proxy.NewProxy(), workerOfflineTimeout: 3 * time.Minute, + maxWorkersPerLicense: maxWorkersPerDefaultLicense, } // Apply options @@ -60,6 +69,20 @@ func New(opts ...Option) (*Controller, error) { opt(controller) } + // Apply environment variables + orchardLicenseTier, ok := os.LookupEnv("ORCHARD_LICENSE_TIER") + if ok { + switch orchardLicenseTier { + case "gold": + controller.maxWorkersPerLicense = maxWorkersPerGoldLicense + case "platinum": + controller.maxWorkersPerLicense = maxWorkersPerPlatinumLicense + default: + return nil, fmt.Errorf("%w: invalid ORCHARD_LICENSE_TIER value: %q", + ErrInitFailed, orchardLicenseTier) + } + } + // Apply defaults if controller.dataDir == nil { return nil, fmt.Errorf("%w: please specify the data directory path with WithDataDir()",