-
Notifications
You must be signed in to change notification settings - Fork 2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add license reporting process (#6309)
- Loading branch information
Showing
8 changed files
with
289 additions
and
48 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
package commonclusterinfo | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/apimachinery/pkg/types" | ||
"k8s.io/client-go/kubernetes" | ||
) | ||
|
||
// This file contains functions for data used in both product telemetry and license reporting | ||
|
||
// GetNodeCount returns the number of nodes in the cluster | ||
func GetNodeCount(ctx context.Context, client kubernetes.Interface) (int, error) { | ||
nodes, err := client.CoreV1().Nodes().List(ctx, metav1.ListOptions{}) | ||
if err != nil { | ||
return 0, err | ||
} | ||
return len(nodes.Items), nil | ||
} | ||
|
||
// GetClusterID returns the UID of the kube-system namespace representing cluster id. | ||
// It returns an error if the underlying k8s API client errors. | ||
func GetClusterID(ctx context.Context, client kubernetes.Interface) (string, error) { | ||
cluster, err := client.CoreV1().Namespaces().Get(ctx, "kube-system", metav1.GetOptions{}) | ||
if err != nil { | ||
return "", err | ||
} | ||
return string(cluster.UID), nil | ||
} | ||
|
||
// GetInstallationID returns the Installation ID of the cluster | ||
func GetInstallationID(ctx context.Context, client kubernetes.Interface, podNSName types.NamespacedName) (_ string, err error) { | ||
defer func() { | ||
if err != nil { | ||
err = fmt.Errorf("error generating InstallationID: %w", err) | ||
} | ||
}() | ||
|
||
pod, err := client.CoreV1().Pods(podNSName.Namespace).Get(ctx, podNSName.Name, metav1.GetOptions{}) | ||
if err != nil { | ||
return "", err | ||
} | ||
podOwner := pod.GetOwnerReferences() | ||
if len(podOwner) != 1 { | ||
return "", fmt.Errorf("expected pod owner reference to be 1, got %d", len(podOwner)) | ||
} | ||
|
||
switch podOwner[0].Kind { | ||
case "ReplicaSet": | ||
rs, err := client.AppsV1().ReplicaSets(podNSName.Namespace).Get(ctx, podOwner[0].Name, metav1.GetOptions{}) | ||
if err != nil { | ||
return "", err | ||
} | ||
rsOwner := rs.GetOwnerReferences() // rsOwner holds information about replica's owner - Deployment object | ||
if len(rsOwner) != 1 { | ||
return "", fmt.Errorf("expected replicaset owner reference to be 1, got %d", len(rsOwner)) | ||
} | ||
return string(rsOwner[0].UID), nil | ||
case "DaemonSet": | ||
return string(podOwner[0].UID), nil | ||
default: | ||
return "", fmt.Errorf("expected pod owner reference to be ReplicaSet or DeamonSet, got %s", podOwner[0].Kind) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
package licensereporting | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"log/slog" | ||
"os" | ||
"path/filepath" | ||
"time" | ||
|
||
nl "github.com/nginxinc/kubernetes-ingress/internal/logger" | ||
|
||
clusterInfo "github.com/nginxinc/kubernetes-ingress/internal/common_cluster_info" | ||
"k8s.io/apimachinery/pkg/types" | ||
"k8s.io/apimachinery/pkg/util/wait" | ||
"k8s.io/client-go/kubernetes" | ||
) | ||
|
||
var ( | ||
reportingDir = "/etc/nginx/reporting" | ||
reportingFile = "tracking.info" | ||
) | ||
|
||
type licenseInfo struct { | ||
Integration string `json:"integration"` | ||
ClusterID string `json:"cluster_id"` | ||
ClusterNodeCount int `json:"cluster_node_count"` | ||
InstallationID string `json:"installation_id"` | ||
} | ||
|
||
func newLicenseInfo(clusterID, installationID string, clusterNodeCount int) *licenseInfo { | ||
return &licenseInfo{ | ||
Integration: "nic", | ||
ClusterID: clusterID, | ||
InstallationID: installationID, | ||
ClusterNodeCount: clusterNodeCount, | ||
} | ||
} | ||
|
||
func writeLicenseInfo(l *slog.Logger, info *licenseInfo) { | ||
jsonData, err := json.Marshal(info) | ||
if err != nil { | ||
nl.Errorf(l, "failed to marshal LicenseInfo to JSON: %v", err) | ||
return | ||
} | ||
filePath := filepath.Join(reportingDir, reportingFile) | ||
if err := os.WriteFile(filePath, jsonData, 0o600); err != nil { | ||
nl.Errorf(l, "failed to write license reporting info to file: %v", err) | ||
} | ||
} | ||
|
||
// LicenseReporter can start the license reporting process | ||
type LicenseReporter struct { | ||
config LicenseReporterConfig | ||
} | ||
|
||
// LicenseReporterConfig contains the information needed for license reporting | ||
type LicenseReporterConfig struct { | ||
Period time.Duration | ||
K8sClientReader kubernetes.Interface | ||
PodNSName types.NamespacedName | ||
} | ||
|
||
// NewLicenseReporter creates a new LicenseReporter | ||
func NewLicenseReporter(client kubernetes.Interface) *LicenseReporter { | ||
return &LicenseReporter{ | ||
config: LicenseReporterConfig{ | ||
Period: 24 * time.Hour, | ||
K8sClientReader: client, | ||
PodNSName: types.NamespacedName{Namespace: os.Getenv("POD_NAMESPACE"), Name: os.Getenv("POD_NAME")}, | ||
}, | ||
} | ||
} | ||
|
||
// 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) | ||
} | ||
|
||
func (lr *LicenseReporter) collectAndWrite(ctx context.Context) { | ||
l := nl.LoggerFromContext(ctx) | ||
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) | ||
if err != nil { | ||
nl.Errorf(l, "Error collecting ClusterNodeCount: %v", err) | ||
} | ||
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
package licensereporting | ||
|
||
import ( | ||
"encoding/json" | ||
"io" | ||
"log/slog" | ||
"os" | ||
"path/filepath" | ||
"testing" | ||
|
||
nic_glog "github.com/nginxinc/kubernetes-ingress/internal/logger/glog" | ||
"github.com/nginxinc/kubernetes-ingress/internal/logger/levels" | ||
|
||
"k8s.io/client-go/kubernetes/fake" | ||
) | ||
|
||
func TestNewLicenseInfo(t *testing.T) { | ||
info := newLicenseInfo("test-cluster", "test-installation", 5) | ||
|
||
if info.Integration != "nic" { | ||
t.Errorf("newLicenseInfo() Integration = %v, want %v", info.Integration, "nic") | ||
} | ||
if info.ClusterID != "test-cluster" { | ||
t.Errorf("newLicenseInfo() ClusterID = %v, want %v", info.ClusterID, "test-cluster") | ||
} | ||
if info.InstallationID != "test-installation" { | ||
t.Errorf("newLicenseInfo() InstallationID = %v, want %v", info.InstallationID, "test-installation") | ||
} | ||
if info.ClusterNodeCount != 5 { | ||
t.Errorf("newLicenseInfo() ClusterNodeCount = %v, want %v", info.ClusterNodeCount, 5) | ||
} | ||
} | ||
|
||
func TestWriteLicenseInfo(t *testing.T) { | ||
tempDir := t.TempDir() | ||
oldReportingDir := reportingDir | ||
reportingDir = tempDir | ||
defer func() { reportingDir = oldReportingDir }() | ||
|
||
l := slog.New(nic_glog.New(io.Discard, &nic_glog.Options{Level: levels.LevelInfo})) | ||
info := newLicenseInfo("test-cluster", "test-installation", 5) | ||
writeLicenseInfo(l, info) | ||
|
||
filePath := filepath.Join(tempDir, reportingFile) | ||
if _, err := os.Stat(filePath); os.IsNotExist(err) { | ||
t.Fatalf("Expected file %s to exist, but it doesn't", filePath) | ||
} | ||
|
||
/* #nosec G304 */ | ||
content, err := os.ReadFile(filePath) | ||
if err != nil { | ||
t.Fatalf("Failed to read file: %v", err) | ||
} | ||
|
||
var readInfo licenseInfo | ||
err = json.Unmarshal(content, &readInfo) | ||
if err != nil { | ||
t.Fatalf("Failed to unmarshal JSON: %v", err) | ||
} | ||
|
||
if readInfo != *info { | ||
t.Errorf("Written info does not match original. Got %+v, want %+v", readInfo, *info) | ||
} | ||
} | ||
|
||
func TestNewLicenseReporter(t *testing.T) { | ||
reporter := NewLicenseReporter(fake.NewSimpleClientset()) | ||
if reporter == nil { | ||
t.Fatal("NewLicenseReporter() returned nil") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.