diff --git a/internal/object/admin.go b/internal/object/admin.go index 9bb5c7e..f8b8e6c 100644 --- a/internal/object/admin.go +++ b/internal/object/admin.go @@ -14,6 +14,7 @@ import ( "github.com/prometheus/client_golang/prometheus/promhttp" stnrv1 "github.com/l7mp/stunner/pkg/apis/v1" + licensemgr "github.com/l7mp/stunner/pkg/licensemanager" ) const DefaultAdminObjectName = "DefaultAdmin" @@ -25,6 +26,8 @@ type Admin struct { MetricsEndpoint, HealthCheckEndpoint string metricsServer, healthCheckServer *http.Server health *http.ServeMux + licenseManager licensemgr.Manager + licenseConfig *stnrv1.LicenseConfig log logging.LeveledLogger } @@ -36,9 +39,10 @@ func NewAdmin(conf stnrv1.Config, dryRun bool, rc ReadinessHandler, status Statu } admin := Admin{ - DryRun: dryRun, - health: http.NewServeMux(), - log: logger.NewLogger("stunner-admin"), + DryRun: dryRun, + health: http.NewServeMux(), + licenseManager: licensemgr.New(logger.NewLogger("license-mgr")), + log: logger.NewLogger("admin"), } admin.log.Tracef("NewAdmin: %s", req.String()) @@ -49,6 +53,7 @@ func NewAdmin(conf stnrv1.Config, dryRun bool, rc ReadinessHandler, status Statu w.WriteHeader(http.StatusOK) w.Write([]byte("{}\n")) //nolint:errcheck }) + // readniness checker calls the checker from the factory admin.health.HandleFunc("/ready", func(w http.ResponseWriter, req *http.Request) { w.Header().Set("Content-Type", "application/json; charset=utf-8") @@ -63,6 +68,7 @@ func NewAdmin(conf stnrv1.Config, dryRun bool, rc ReadinessHandler, status Statu http.StatusOK, "READY"))) } }) + // status handler returns the status admin.health.HandleFunc("/status", func(w http.ResponseWriter, req *http.Request) { w.Header().Set("Content-Type", "application/json; charset=utf-8") @@ -118,6 +124,9 @@ func (a *Admin) Reconcile(conf stnrv1.Config) error { return err } + a.licenseManager.Update(req.LicenseConfig) + a.licenseConfig = req.LicenseConfig + return nil } @@ -144,6 +153,7 @@ func (a *Admin) GetConfig() stnrv1.Config { LogLevel: a.LogLevel, MetricsEndpoint: a.MetricsEndpoint, HealthCheckEndpoint: &h, + LicenseConfig: a.licenseConfig, } } @@ -167,6 +177,8 @@ func (a *Admin) Close() error { } } + a.licenseManager.Close() + return nil } @@ -177,6 +189,7 @@ func (a *Admin) Status() stnrv1.Status { LogLevel: a.LogLevel, MetricsEndpoint: a.MetricsEndpoint, HealthCheckEndpoint: a.HealthCheckEndpoint, + LicensingInfo: a.licenseManager.Status(), } // add licensing status here diff --git a/internal/object/auth.go b/internal/object/auth.go index aba8e04..d804ec5 100644 --- a/internal/object/auth.go +++ b/internal/object/auth.go @@ -23,7 +23,7 @@ func NewAuth(conf stnrv1.Config, logger logging.LoggerFactory) (Object, error) { return nil, stnrv1.ErrInvalidConf } - auth := Auth{Log: logger.NewLogger("stunner-auth")} + auth := Auth{Log: logger.NewLogger("auth")} auth.Log.Tracef("NewAuth: %s", req.String()) if err := auth.Reconcile(req); err != nil && !errors.Is(err, ErrRestartRequired) { diff --git a/internal/object/cluster.go b/internal/object/cluster.go index 1aec7b0..8d69898 100644 --- a/internal/object/cluster.go +++ b/internal/object/cluster.go @@ -44,7 +44,7 @@ func NewCluster(conf stnrv1.Config, resolver resolver.DnsResolver, logger loggin Domains: []string{}, Resolver: resolver, logger: logger, - log: logger.NewLogger(fmt.Sprintf("stunner-cluster-%s", req.Name)), + log: logger.NewLogger(fmt.Sprintf("cluster-%s", req.Name)), } c.log.Tracef("NewCluster: %s", req.String()) diff --git a/internal/object/listener.go b/internal/object/listener.go index 2fb059a..5269cc8 100644 --- a/internal/object/listener.go +++ b/internal/object/listener.go @@ -55,7 +55,7 @@ func NewListener(conf stnrv1.Config, net transport.Net, realmHandler RealmHandle getRealm: realmHandler, Conns: []any{}, logger: logger, - log: logger.NewLogger(fmt.Sprintf("stunner-listener-%s", req.Name)), + log: logger.NewLogger(fmt.Sprintf("listener-%s", req.Name)), } l.log.Tracef("NewListener: %s", req.String()) diff --git a/pkg/apis/v1/admin.go b/pkg/apis/v1/admin.go index 9f95ff3..c69187c 100644 --- a/pkg/apis/v1/admin.go +++ b/pkg/apis/v1/admin.go @@ -25,6 +25,18 @@ type AdminConfig struct { // health-checking at `http://0.0.0.0:8086`. Set to a pointer to an empty string to disable // health-checking. HealthCheckEndpoint *string `json:"healthcheck_endpoint,omitempty"` + // LicenseConfig describes the licensing info to be used to check subscription status with + // the license server. + LicenseConfig *LicenseConfig `json:"license_config,omitempty"` +} + +// Licensing info to be used to check subscription status with the license server. +type LicenseConfig struct { + // Key is a comma-separated list of unlocked features plus a time-window during which the + // key is considered valid. + Key string `json:"key"` + // HMAC is a hash-based message authentication code for validating the license key. + HMAC string `json:"hmac"` } // Validate checks a configuration and injects defaults. @@ -92,21 +104,39 @@ func (req *AdminConfig) String() string { if req.HealthCheckEndpoint != nil { status = append(status, fmt.Sprintf("health-check=%q", *req.HealthCheckEndpoint)) } + status = append(status, fmt.Sprintf("license_info=%s", LicensingStatus(req.LicenseConfig))) + return fmt.Sprintf("admin:{%s}", strings.Join(status, ",")) } +func LicensingStatus(req *LicenseConfig) string { + if req != nil { + key := "" + if req != nil { + key = "" + } + pass := "" + if req != nil { + pass = "" + } + return fmt.Sprintf("{key=%s,pass=%s}", key, pass) + } + + return "" +} + // AdminStatus represents the administrative status. type AdminStatus struct { Name string `json:"name,omitempty"` LogLevel string `json:"loglevel,omitempty"` MetricsEndpoint string `json:"metrics_endpoint,omitempty"` HealthCheckEndpoint string `json:"healthcheck_endpoint,omitempty"` - // licencing status comes here + LicensingInfo string `json:"licensing_info,omitempty"` } // String returns a string reprsentation of the administrative status. func (a *AdminStatus) String() string { - status := []string{} + status := []string{fmt.Sprintf("id=%q", a.Name)} if a.LogLevel != "" { status = append(status, fmt.Sprintf("logLevel=%q", a.LogLevel)) } @@ -116,8 +146,9 @@ func (a *AdminStatus) String() string { if a.HealthCheckEndpoint != "" { status = append(status, fmt.Sprintf("health-check=%q", a.HealthCheckEndpoint)) } + if a.LicensingInfo != "" { + status = append(status, fmt.Sprintf("license-info=%s", a.LicensingInfo)) + } - // add licencing status here - - return fmt.Sprintf("%s:{%s}", a.Name, strings.Join(status, ",")) + return fmt.Sprintf("admin:{%s}", strings.Join(status, ",")) } diff --git a/pkg/apis/v1/stunner.go b/pkg/apis/v1/stunner.go index ebcff12..03d9e22 100644 --- a/pkg/apis/v1/stunner.go +++ b/pkg/apis/v1/stunner.go @@ -236,8 +236,8 @@ func (s *StunnerStatus) String() string { cs = append(cs, c.String()) } - return fmt.Sprintf("%s/%s/%s/%s/allocs:%d/status=%s", - s.Admin.String(), s.Auth.String(), ls, cs, s.AllocationCount, s.Status) + return fmt.Sprintf("%s/%s/%s/%s/allocs:%d/status=%s", s.Admin.String(), s.Auth.String(), + ls, cs, s.AllocationCount, s.Status) } // String summarizes the status. diff --git a/pkg/licensemanager/manager.go b/pkg/licensemanager/manager.go new file mode 100644 index 0000000..7ede5ea --- /dev/null +++ b/pkg/licensemanager/manager.go @@ -0,0 +1,41 @@ +package licensemanager + +import ( + "github.com/pion/logging" + + stnrv1 "github.com/l7mp/stunner/pkg/apis/v1" +) + +var constructor = NewStub +var _ Manager = &baseManager{} + +type Manager interface { + Update(config *stnrv1.LicenseConfig) + Close() + Validate(feature string) bool + GetTier() string + GetConfig() *stnrv1.LicenseConfig + Status() string +} + +func New(log logging.LeveledLogger) Manager { + return constructor(log) +} + +// baseManager implements the basic functionality so that all license manager implementations can embed it +type baseManager struct { + config *stnrv1.LicenseConfig + log logging.LeveledLogger +} + +func newBaseManager(log logging.LeveledLogger) baseManager { + m := baseManager{log: log} + return m +} + +func (m *baseManager) Update(config *stnrv1.LicenseConfig) { m.config = config } +func (m *baseManager) Close() {} +func (m *baseManager) Validate(feature string) bool { return false } +func (m *baseManager) GetTier() string { return "" } +func (m *baseManager) GetConfig() *stnrv1.LicenseConfig { return m.config } +func (m *baseManager) Status() string { return "" } diff --git a/pkg/licensemanager/stub.go b/pkg/licensemanager/stub.go new file mode 100644 index 0000000..1eeefba --- /dev/null +++ b/pkg/licensemanager/stub.go @@ -0,0 +1,29 @@ +package licensemanager + +import ( + "fmt" + + stnrv1 "github.com/l7mp/stunner/pkg/apis/v1" + "github.com/pion/logging" +) + +var _ Manager = &Stub{} + +// Stub is the default license manager that implements the free/open-source tier. +type Stub struct{ baseManager } + +func NewStub(log logging.LeveledLogger) Manager { + s := &Stub{baseManager: newBaseManager(log)} + return s +} + +func (s *Stub) Update(config *stnrv1.LicenseConfig) { + s.log.Tracef("Licensing status update triggered using config %q", stnrv1.LicensingStatus(config)) + s.baseManager.Update(config) +} + +func (s *Stub) GetTier() string { return "free" } + +func (s *Stub) Status() string { + return fmt.Sprintf("{tier=%q,conf=%s}", s.GetTier(), stnrv1.LicensingStatus(s.GetConfig())) +}