Skip to content

Commit

Permalink
consume license expiry endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
j1m-ryan authored and pdabelf5 committed Dec 4, 2024
1 parent 406d168 commit 58dac50
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 11 deletions.
3 changes: 3 additions & 0 deletions cmd/nginx-ingress/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down
57 changes: 46 additions & 11 deletions internal/license_reporting/license_reporting.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"time"

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

Check failure on line 13 in internal/license_reporting/license_reporting.go

View workflow job for this annotation

GitHub Actions / Unit Tests

no required module provides package github.com/nginxinc/nginx-plus-go-client/v2/client; to add it:

clusterInfo "github.com/nginxinc/kubernetes-ingress/internal/common_cluster_info"
api_v1 "k8s.io/api/core/v1"
Expand All @@ -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"
Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand All @@ -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
}
100 changes: 100 additions & 0 deletions internal/license_reporting/license_reporting_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
}
}
}

0 comments on commit 58dac50

Please sign in to comment.