Skip to content

Commit df9b043

Browse files
authored
Update license reporting (nginx#6917)
1 parent 39b285d commit df9b043

File tree

4 files changed

+169
-20
lines changed

4 files changed

+169
-20
lines changed

cmd/nginx-ingress/main.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ func main() {
120120
var licenseReporter *license_reporting.LicenseReporter
121121

122122
if *nginxPlus {
123-
licenseReporter = license_reporting.NewLicenseReporter(kubeClient)
123+
licenseReporter = license_reporting.NewLicenseReporter(kubeClient, eventRecorder, pod)
124124
}
125125

126126
nginxManager, useFakeNginxManager := createNginxManager(ctx, managerCollector, licenseReporter)
@@ -214,6 +214,9 @@ func main() {
214214
process := startChildProcesses(nginxManager, appProtectV5)
215215

216216
plusClient := createPlusClient(ctx, *nginxPlus, useFakeNginxManager, nginxManager)
217+
if *nginxPlus {
218+
licenseReporter.Config.PlusClient = plusClient
219+
}
217220

218221
plusCollector, syslogListener, latencyCollector := createPlusAndLatencyCollectors(ctx, registry, constLabels, kubeClient, plusClient, staticCfgParams.NginxServiceMesh)
219222
cnf := configs.NewConfigurator(configs.ConfiguratorParams{

internal/license_reporting/license_reporting.go

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,25 @@ package licensereporting
33
import (
44
"context"
55
"encoding/json"
6+
"fmt"
67
"log/slog"
78
"os"
89
"path/filepath"
910
"time"
1011

1112
nl "github.com/nginxinc/kubernetes-ingress/internal/logger"
13+
"github.com/nginxinc/nginx-plus-go-client/v2/client"
1214

1315
clusterInfo "github.com/nginxinc/kubernetes-ingress/internal/common_cluster_info"
16+
api_v1 "k8s.io/api/core/v1"
1417
"k8s.io/apimachinery/pkg/types"
1518
"k8s.io/apimachinery/pkg/util/wait"
1619
"k8s.io/client-go/kubernetes"
20+
"k8s.io/client-go/tools/record"
1721
)
1822

23+
const expiryThreshold = 30 * (time.Hour * 24)
24+
1925
var (
2026
reportingDir = "/etc/nginx/reporting"
2127
reportingFile = "tracking.info"
@@ -51,46 +57,91 @@ func writeLicenseInfo(l *slog.Logger, info *licenseInfo) {
5157

5258
// LicenseReporter can start the license reporting process
5359
type LicenseReporter struct {
54-
config LicenseReporterConfig
60+
Config LicenseReporterConfig
5561
}
5662

5763
// LicenseReporterConfig contains the information needed for license reporting
5864
type LicenseReporterConfig struct {
5965
Period time.Duration
6066
K8sClientReader kubernetes.Interface
6167
PodNSName types.NamespacedName
68+
EventLog record.EventRecorder
69+
Pod *api_v1.Pod
70+
PlusClient *client.NginxClient
6271
}
6372

6473
// NewLicenseReporter creates a new LicenseReporter
65-
func NewLicenseReporter(client kubernetes.Interface) *LicenseReporter {
74+
func NewLicenseReporter(client kubernetes.Interface, eventLog record.EventRecorder, pod *api_v1.Pod) *LicenseReporter {
6675
return &LicenseReporter{
67-
config: LicenseReporterConfig{
68-
Period: 24 * time.Hour,
76+
Config: LicenseReporterConfig{
77+
EventLog: eventLog,
78+
Period: time.Hour,
6979
K8sClientReader: client,
7080
PodNSName: types.NamespacedName{Namespace: os.Getenv("POD_NAMESPACE"), Name: os.Getenv("POD_NAME")},
81+
Pod: pod,
7182
},
7283
}
7384
}
7485

7586
// Start begins the license report writer process for NIC
7687
func (lr *LicenseReporter) Start(ctx context.Context) {
77-
wait.JitterUntilWithContext(ctx, lr.collectAndWrite, lr.config.Period, 0.1, true)
88+
wait.JitterUntilWithContext(ctx, lr.collectAndWrite, lr.Config.Period, 0.1, true)
7889
}
7990

8091
func (lr *LicenseReporter) collectAndWrite(ctx context.Context) {
8192
l := nl.LoggerFromContext(ctx)
82-
clusterID, err := clusterInfo.GetClusterID(ctx, lr.config.K8sClientReader)
93+
clusterID, err := clusterInfo.GetClusterID(ctx, lr.Config.K8sClientReader)
8394
if err != nil {
8495
nl.Errorf(l, "Error collecting ClusterIDS: %v", err)
8596
}
86-
nodeCount, err := clusterInfo.GetNodeCount(ctx, lr.config.K8sClientReader)
97+
nodeCount, err := clusterInfo.GetNodeCount(ctx, lr.Config.K8sClientReader)
8798
if err != nil {
8899
nl.Errorf(l, "Error collecting ClusterNodeCount: %v", err)
89100
}
90-
installationID, err := clusterInfo.GetInstallationID(ctx, lr.config.K8sClientReader, lr.config.PodNSName)
101+
installationID, err := clusterInfo.GetInstallationID(ctx, lr.Config.K8sClientReader, lr.Config.PodNSName)
91102
if err != nil {
92103
nl.Errorf(l, "Error collecting InstallationID: %v", err)
93104
}
94105
info := newLicenseInfo(clusterID, installationID, nodeCount)
95106
writeLicenseInfo(l, info)
107+
if lr.Config.PlusClient != nil {
108+
lr.checkLicenseExpiry(ctx)
109+
}
110+
}
111+
112+
func (lr *LicenseReporter) checkLicenseExpiry(ctx context.Context) {
113+
l := nl.LoggerFromContext(ctx)
114+
licenseData, err := lr.Config.PlusClient.GetNginxLicense(context.Background())
115+
if err != nil {
116+
nl.Errorf(l, "could not get license data, %v", err)
117+
return
118+
}
119+
var licenseEventText string
120+
if expiring, days := licenseExpiring(licenseData); expiring {
121+
licenseEventText = fmt.Sprintf("License expiring in %d day(s)", days)
122+
nl.Warn(l, licenseEventText)
123+
lr.Config.EventLog.Event(lr.Config.Pod, api_v1.EventTypeWarning, "LicenseExpiry", licenseEventText)
124+
}
125+
var usageGraceEventText string
126+
if ending, days := usageGraceEnding(licenseData); ending {
127+
usageGraceEventText = fmt.Sprintf("Usage reporting grace period ending in %d day(s)", days)
128+
nl.Warn(l, usageGraceEventText)
129+
lr.Config.EventLog.Event(lr.Config.Pod, api_v1.EventTypeWarning, "UsageGraceEnding", usageGraceEventText)
130+
}
131+
}
132+
133+
func licenseExpiring(licenseData *client.NginxLicense) (bool, int64) {
134+
expiry := time.Unix(int64(licenseData.ActiveTill), 0) //nolint:gosec
135+
now := time.Now()
136+
timeUntilLicenseExpiry := expiry.Sub(now)
137+
daysUntilLicenseExpiry := int64(timeUntilLicenseExpiry.Hours() / 24)
138+
expiryDays := int64(expiryThreshold.Hours() / 24)
139+
return daysUntilLicenseExpiry < expiryDays, daysUntilLicenseExpiry
140+
}
141+
142+
func usageGraceEnding(licenseData *client.NginxLicense) (bool, int64) {
143+
grace := time.Second * time.Duration(licenseData.Reporting.Grace) //nolint:gosec
144+
daysUntilUsageGraceEnds := int64(grace.Hours() / 24)
145+
expiryDays := int64(expiryThreshold.Hours() / 24)
146+
return daysUntilUsageGraceEnds < expiryDays, daysUntilUsageGraceEnds
96147
}

internal/license_reporting/license_reporting_test.go

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,15 @@ import (
77
"os"
88
"path/filepath"
99
"testing"
10+
"time"
1011

1112
nic_glog "github.com/nginxinc/kubernetes-ingress/internal/logger/glog"
1213
"github.com/nginxinc/kubernetes-ingress/internal/logger/levels"
14+
"github.com/nginxinc/nginx-plus-go-client/v2/client"
1315

16+
v1 "k8s.io/api/core/v1"
1417
"k8s.io/client-go/kubernetes/fake"
18+
"k8s.io/client-go/tools/record"
1519
)
1620

1721
func TestNewLicenseInfo(t *testing.T) {
@@ -64,8 +68,106 @@ func TestWriteLicenseInfo(t *testing.T) {
6468
}
6569

6670
func TestNewLicenseReporter(t *testing.T) {
67-
reporter := NewLicenseReporter(fake.NewSimpleClientset())
71+
reporter := NewLicenseReporter(fake.NewSimpleClientset(), record.NewFakeRecorder(2048), &v1.Pod{})
6872
if reporter == nil {
6973
t.Fatal("NewLicenseReporter() returned nil")
7074
}
7175
}
76+
77+
func TestLicenseExpiring(t *testing.T) {
78+
t.Parallel()
79+
80+
testCases := []struct {
81+
licenseData client.NginxLicense
82+
belowExpiringThreshold bool
83+
days int64
84+
name string
85+
}{
86+
{
87+
licenseData: client.NginxLicense{
88+
ActiveTill: uint64(time.Now().Add(time.Hour).Unix()), //nolint:gosec
89+
},
90+
belowExpiringThreshold: true,
91+
days: 0,
92+
name: "License expires in 1 hour",
93+
},
94+
{
95+
licenseData: client.NginxLicense{
96+
ActiveTill: uint64(time.Now().Add(-time.Hour).Unix()), //nolint:gosec
97+
},
98+
belowExpiringThreshold: true,
99+
days: 0,
100+
name: "License expired 1 hour ago",
101+
},
102+
{
103+
licenseData: client.NginxLicense{
104+
ActiveTill: uint64(time.Now().Add(time.Hour * 24 * 31).Unix()), //nolint:gosec
105+
},
106+
belowExpiringThreshold: false,
107+
days: 30, // Rounds down
108+
name: "License expires in 31 days",
109+
},
110+
}
111+
112+
for _, tc := range testCases {
113+
actualExpiring, actualDays := licenseExpiring(&tc.licenseData)
114+
if actualExpiring != tc.belowExpiringThreshold {
115+
t.Fatalf("%s: Expected different value for expiring %t", tc.name, tc.belowExpiringThreshold)
116+
}
117+
if actualDays != tc.days {
118+
t.Fatalf("%s: Expected different value for days %d != %d", tc.name, actualDays, tc.days)
119+
}
120+
}
121+
}
122+
123+
func TestUsageGraceEnding(t *testing.T) {
124+
t.Parallel()
125+
126+
testCases := []struct {
127+
licenseData client.NginxLicense
128+
belowExpiringThreshold bool
129+
days int64
130+
name string
131+
}{
132+
{
133+
licenseData: client.NginxLicense{
134+
Reporting: client.LicenseReporting{
135+
Grace: 3600, // seconds
136+
},
137+
},
138+
belowExpiringThreshold: true,
139+
days: 0,
140+
name: "Grace period ends in an hour",
141+
},
142+
{
143+
licenseData: client.NginxLicense{
144+
Reporting: client.LicenseReporting{
145+
Grace: 60 * 60 * 24 * 31, // 31 days
146+
},
147+
},
148+
belowExpiringThreshold: false,
149+
days: 31,
150+
name: "Grace period ends 31 days",
151+
},
152+
{
153+
licenseData: client.NginxLicense{
154+
Reporting: client.LicenseReporting{
155+
Grace: 0,
156+
},
157+
},
158+
belowExpiringThreshold: true,
159+
days: 0,
160+
name: "Grace period ended",
161+
},
162+
}
163+
164+
for _, tc := range testCases {
165+
actualEnding, actualDays := usageGraceEnding(&tc.licenseData)
166+
if actualEnding != tc.belowExpiringThreshold {
167+
t.Fatalf("%s: Expected different value for expiring %t", tc.name, tc.belowExpiringThreshold)
168+
}
169+
if actualDays != tc.days {
170+
t.Fatalf("%s: Expected different value for days %d != %d", tc.name, actualDays, tc.days)
171+
}
172+
}
173+
}

internal/nginx/manager.go

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -303,16 +303,9 @@ func (lm *LocalManager) ClearAppProtectFolder(name string) {
303303
// Start starts NGINX.
304304
func (lm *LocalManager) Start(done chan error) {
305305
if lm.nginxPlus {
306-
isR33OrGreater, versionErr := lm.Version().PlusGreaterThanOrEqualTo("nginx-plus-r33")
307-
if versionErr != nil {
308-
nl.Errorf(lm.logger, "Error determining whether nginx version is >= r33: %v", versionErr)
309-
}
310-
if isR33OrGreater {
311-
ctx, cancel := context.WithCancel(context.Background())
312-
nl.ContextWithLogger(ctx, lm.logger)
313-
go lm.licenseReporter.Start(ctx)
314-
lm.licenseReporterCancel = cancel
315-
}
306+
ctx, cancel := context.WithCancel(context.Background())
307+
go lm.licenseReporter.Start(nl.ContextWithLogger(ctx, lm.logger))
308+
lm.licenseReporterCancel = cancel
316309
}
317310

318311
nl.Debug(lm.logger, "Starting nginx")

0 commit comments

Comments
 (0)