diff --git a/cmd/nginx-ingress/main.go b/cmd/nginx-ingress/main.go index c82c37e1e4..8b0c78153f 100644 --- a/cmd/nginx-ingress/main.go +++ b/cmd/nginx-ingress/main.go @@ -218,6 +218,9 @@ func main() { process := startChildProcesses(nginxManager, appProtectV5) plusClient := createPlusClient(ctx, *nginxPlus, useFakeNginxManager, nginxManager) + if *nginxPlus { + licenseReporter.Config.PlusClient = plusClient + } plusCollector, syslogListener, latencyCollector := createPlusAndLatencyCollectors(ctx, registry, constLabels, kubeClient, plusClient, staticCfgParams.NginxServiceMesh) cnf := configs.NewConfigurator(configs.ConfiguratorParams{ diff --git a/internal/license_reporting/license_reporting.go b/internal/license_reporting/license_reporting.go index 665a461d4a..2626272d80 100644 --- a/internal/license_reporting/license_reporting.go +++ b/internal/license_reporting/license_reporting.go @@ -10,6 +10,7 @@ import ( "time" nl "github.com/nginxinc/kubernetes-ingress/internal/logger" + "github.com/nginxinc/nginx-plus-go-client/v2/client" clusterInfo "github.com/nginxinc/kubernetes-ingress/internal/common_cluster_info" api_v1 "k8s.io/api/core/v1" @@ -19,6 +20,8 @@ import ( "k8s.io/client-go/tools/record" ) +const expiryThreshold = 30 * (time.Hour * 24) + var ( reportingDir = "/etc/nginx/reporting" reportingFile = "tracking.info" @@ -54,7 +57,7 @@ func writeLicenseInfo(l *slog.Logger, info *licenseInfo) { // LicenseReporter can start the license reporting process type LicenseReporter struct { - config LicenseReporterConfig + Config LicenseReporterConfig } // LicenseReporterConfig contains the information needed for license reporting @@ -64,12 +67,13 @@ type LicenseReporterConfig struct { PodNSName types.NamespacedName EventLog record.EventRecorder Pod *api_v1.Pod + PlusClient *client.NginxClient } // NewLicenseReporter creates a new LicenseReporter func NewLicenseReporter(client kubernetes.Interface, eventLog record.EventRecorder, pod *api_v1.Pod) *LicenseReporter { return &LicenseReporter{ - config: LicenseReporterConfig{ + Config: LicenseReporterConfig{ EventLog: eventLog, Period: time.Hour, K8sClientReader: client, @@ -81,32 +85,63 @@ func NewLicenseReporter(client kubernetes.Interface, eventLog record.EventRecord // Start begins the license report writer process for NIC func (lr *LicenseReporter) Start(ctx context.Context) { - wait.JitterUntilWithContext(ctx, lr.collectAndWrite, lr.config.Period, 0.1, true) + wait.JitterUntilWithContext(ctx, lr.collectAndWrite, lr.Config.Period, 0.1, true) } func (lr *LicenseReporter) collectAndWrite(ctx context.Context) { l := nl.LoggerFromContext(ctx) - clusterID, err := clusterInfo.GetClusterID(ctx, lr.config.K8sClientReader) + clusterID, err := clusterInfo.GetClusterID(ctx, lr.Config.K8sClientReader) if err != nil { nl.Errorf(l, "Error collecting ClusterIDS: %v", err) } - nodeCount, err := clusterInfo.GetNodeCount(ctx, lr.config.K8sClientReader) + nodeCount, err := clusterInfo.GetNodeCount(ctx, lr.Config.K8sClientReader) if err != nil { nl.Errorf(l, "Error collecting ClusterNodeCount: %v", err) } - installationID, err := clusterInfo.GetInstallationID(ctx, lr.config.K8sClientReader, lr.config.PodNSName) + installationID, err := clusterInfo.GetInstallationID(ctx, lr.Config.K8sClientReader, lr.Config.PodNSName) if err != nil { nl.Errorf(l, "Error collecting InstallationID: %v", err) } info := newLicenseInfo(clusterID, installationID, nodeCount) writeLicenseInfo(l, info) - lr.checkLicenseExpiry(ctx) + if lr.Config.PlusClient != nil { + lr.checkLicenseExpiry(ctx) + } } func (lr *LicenseReporter) checkLicenseExpiry(ctx context.Context) { l := nl.LoggerFromContext(ctx) - days := 5 - eventText := fmt.Sprintf("Expires in %d days", days) - nl.Debug(l, eventText) - lr.config.EventLog.Event(lr.config.Pod, api_v1.EventTypeNormal, "ExpiryCheck", eventText) + licenseData, err := lr.Config.PlusClient.GetNginxLicense(context.Background()) + if err != nil { + nl.Errorf(l, "could not get license data, %v", err) + return + } + var licenseEventText string + if expiring, days := licenseExpiring(licenseData); expiring { + licenseEventText = fmt.Sprintf("License expiring in %d day(s)", days) + nl.Warn(l, licenseEventText) + lr.Config.EventLog.Event(lr.Config.Pod, api_v1.EventTypeWarning, "LicenseExpiry", licenseEventText) + } + var usageGraceEventText string + if ending, days := usageGraceEnding(licenseData); ending { + usageGraceEventText = fmt.Sprintf("Usage reporting grace period ending in %d day(s)", days) + nl.Warn(l, usageGraceEventText) + lr.Config.EventLog.Event(lr.Config.Pod, api_v1.EventTypeWarning, "UsageGraceEnding", usageGraceEventText) + } +} + +func licenseExpiring(licenseData *client.NginxLicense) (bool, int64) { + expiry := time.Unix(int64(licenseData.ActiveTill), 0) //nolint:gosec + now := time.Now() + timeUntilLicenseExpiry := expiry.Sub(now) + daysUntilLicenseExpiry := int64(timeUntilLicenseExpiry.Hours() / 24) + expiryDays := int64(expiryThreshold.Hours() / 24) + return daysUntilLicenseExpiry < expiryDays, daysUntilLicenseExpiry +} + +func usageGraceEnding(licenseData *client.NginxLicense) (bool, int64) { + grace := time.Second * time.Duration(licenseData.Reporting.Grace) //nolint:gosec + daysUntilUsageGraceEnds := int64(grace.Hours() / 24) + expiryDays := int64(expiryThreshold.Hours() / 24) + return daysUntilUsageGraceEnds < expiryDays, daysUntilUsageGraceEnds } diff --git a/internal/license_reporting/license_reporting_test.go b/internal/license_reporting/license_reporting_test.go index e498f309de..90170fdb46 100644 --- a/internal/license_reporting/license_reporting_test.go +++ b/internal/license_reporting/license_reporting_test.go @@ -7,9 +7,11 @@ import ( "os" "path/filepath" "testing" + "time" nic_glog "github.com/nginxinc/kubernetes-ingress/internal/logger/glog" "github.com/nginxinc/kubernetes-ingress/internal/logger/levels" + "github.com/nginxinc/nginx-plus-go-client/v2/client" v1 "k8s.io/api/core/v1" "k8s.io/client-go/kubernetes/fake" @@ -71,3 +73,101 @@ func TestNewLicenseReporter(t *testing.T) { t.Fatal("NewLicenseReporter() returned nil") } } + +func TestLicenseExpiring(t *testing.T) { + t.Parallel() + + testCases := []struct { + licenseData client.NginxLicense + belowExpiringThreshold bool + days int64 + name string + }{ + { + licenseData: client.NginxLicense{ + ActiveTill: uint64(time.Now().Add(time.Hour).Unix()), //nolint:gosec + }, + belowExpiringThreshold: true, + days: 0, + name: "License expires in 1 hour", + }, + { + licenseData: client.NginxLicense{ + ActiveTill: uint64(time.Now().Add(-time.Hour).Unix()), //nolint:gosec + }, + belowExpiringThreshold: true, + days: 0, + name: "License expired 1 hour ago", + }, + { + licenseData: client.NginxLicense{ + ActiveTill: uint64(time.Now().Add(time.Hour * 24 * 31).Unix()), //nolint:gosec + }, + belowExpiringThreshold: false, + days: 30, // Rounds down + name: "License expires in 31 days", + }, + } + + for _, tc := range testCases { + actualExpiring, actualDays := licenseExpiring(&tc.licenseData) + if actualExpiring != tc.belowExpiringThreshold { + t.Fatalf("%s: Expected different value for expiring %t", tc.name, tc.belowExpiringThreshold) + } + if actualDays != tc.days { + t.Fatalf("%s: Expected different value for days %d != %d", tc.name, actualDays, tc.days) + } + } +} + +func TestUsageGraceEnding(t *testing.T) { + t.Parallel() + + testCases := []struct { + licenseData client.NginxLicense + belowExpiringThreshold bool + days int64 + name string + }{ + { + licenseData: client.NginxLicense{ + Reporting: client.LicenseReporting{ + Grace: 3600, // seconds + }, + }, + belowExpiringThreshold: true, + days: 0, + name: "Grace period ends in an hour", + }, + { + licenseData: client.NginxLicense{ + Reporting: client.LicenseReporting{ + Grace: 60 * 60 * 24 * 31, // 31 days + }, + }, + belowExpiringThreshold: false, + days: 31, + name: "Grace period ends 31 days", + }, + { + licenseData: client.NginxLicense{ + Reporting: client.LicenseReporting{ + Grace: 0, + }, + }, + belowExpiringThreshold: true, + days: 0, + name: "Grace period ended", + }, + } + + for _, tc := range testCases { + actualEnding, actualDays := usageGraceEnding(&tc.licenseData) + if actualEnding != tc.belowExpiringThreshold { + t.Fatalf("%s: Expected different value for expiring %t", tc.name, tc.belowExpiringThreshold) + } + if actualDays != tc.days { + t.Fatalf("%s: Expected different value for days %d != %d", tc.name, actualDays, tc.days) + } + } +}