Skip to content

Commit

Permalink
Update license reporting (#6917)
Browse files Browse the repository at this point in the history
  • Loading branch information
pdabelf5 authored Dec 5, 2024
1 parent 39b285d commit df9b043
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 20 deletions.
5 changes: 4 additions & 1 deletion cmd/nginx-ingress/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ func main() {
var licenseReporter *license_reporting.LicenseReporter

if *nginxPlus {
licenseReporter = license_reporting.NewLicenseReporter(kubeClient)
licenseReporter = license_reporting.NewLicenseReporter(kubeClient, eventRecorder, pod)
}

nginxManager, useFakeNginxManager := createNginxManager(ctx, managerCollector, licenseReporter)
Expand Down Expand Up @@ -214,6 +214,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{
Expand Down
67 changes: 59 additions & 8 deletions internal/license_reporting/license_reporting.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,25 @@ package licensereporting
import (
"context"
"encoding/json"
"fmt"
"log/slog"
"os"
"path/filepath"
"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"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/record"
)

const expiryThreshold = 30 * (time.Hour * 24)

var (
reportingDir = "/etc/nginx/reporting"
reportingFile = "tracking.info"
Expand Down Expand Up @@ -51,46 +57,91 @@ 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
type LicenseReporterConfig struct {
Period time.Duration
K8sClientReader kubernetes.Interface
PodNSName types.NamespacedName
EventLog record.EventRecorder
Pod *api_v1.Pod
PlusClient *client.NginxClient
}

// NewLicenseReporter creates a new LicenseReporter
func NewLicenseReporter(client kubernetes.Interface) *LicenseReporter {
func NewLicenseReporter(client kubernetes.Interface, eventLog record.EventRecorder, pod *api_v1.Pod) *LicenseReporter {
return &LicenseReporter{
config: LicenseReporterConfig{
Period: 24 * time.Hour,
Config: LicenseReporterConfig{
EventLog: eventLog,
Period: time.Hour,
K8sClientReader: client,
PodNSName: types.NamespacedName{Namespace: os.Getenv("POD_NAMESPACE"), Name: os.Getenv("POD_NAME")},
Pod: pod,
},
}
}

// 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)
if lr.Config.PlusClient != nil {
lr.checkLicenseExpiry(ctx)
}
}

func (lr *LicenseReporter) checkLicenseExpiry(ctx context.Context) {
l := nl.LoggerFromContext(ctx)
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
}
104 changes: 103 additions & 1 deletion internal/license_reporting/license_reporting_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@ 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"
"k8s.io/client-go/tools/record"
)

func TestNewLicenseInfo(t *testing.T) {
Expand Down Expand Up @@ -64,8 +68,106 @@ func TestWriteLicenseInfo(t *testing.T) {
}

func TestNewLicenseReporter(t *testing.T) {
reporter := NewLicenseReporter(fake.NewSimpleClientset())
reporter := NewLicenseReporter(fake.NewSimpleClientset(), record.NewFakeRecorder(2048), &v1.Pod{})
if reporter == nil {
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)
}
}
}
13 changes: 3 additions & 10 deletions internal/nginx/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -303,16 +303,9 @@ func (lm *LocalManager) ClearAppProtectFolder(name string) {
// Start starts NGINX.
func (lm *LocalManager) Start(done chan error) {
if lm.nginxPlus {
isR33OrGreater, versionErr := lm.Version().PlusGreaterThanOrEqualTo("nginx-plus-r33")
if versionErr != nil {
nl.Errorf(lm.logger, "Error determining whether nginx version is >= r33: %v", versionErr)
}
if isR33OrGreater {
ctx, cancel := context.WithCancel(context.Background())
nl.ContextWithLogger(ctx, lm.logger)
go lm.licenseReporter.Start(ctx)
lm.licenseReporterCancel = cancel
}
ctx, cancel := context.WithCancel(context.Background())
go lm.licenseReporter.Start(nl.ContextWithLogger(ctx, lm.logger))
lm.licenseReporterCancel = cancel
}

nl.Debug(lm.logger, "Starting nginx")
Expand Down

0 comments on commit df9b043

Please sign in to comment.