diff --git a/internal/mode/static/telemetry/collector.go b/internal/mode/static/telemetry/collector.go index b4240543ed..74d8d2ebf1 100644 --- a/internal/mode/static/telemetry/collector.go +++ b/internal/mode/static/telemetry/collector.go @@ -52,8 +52,9 @@ type ProjectMetadata struct { type Data struct { ProjectMetadata ProjectMetadata ClusterID string - ImageSource string Arch string + DeploymentID string + ImageSource string NGFResourceCounts NGFResourceCounts NodeCount int NGFReplicaCount int @@ -101,11 +102,21 @@ func (c DataCollectorImpl) Collect(ctx context.Context) (Data, error) { return Data{}, fmt.Errorf("failed to collect NGF resource counts: %w", err) } - ngfReplicaCount, err := collectNGFReplicaCount(ctx, c.cfg.K8sClientReader, c.cfg.PodNSName) + replicaSet, err := getPodReplicaSet(ctx, c.cfg.K8sClientReader, c.cfg.PodNSName) + if err != nil { + return Data{}, fmt.Errorf("failed to get replica set for pod %s: %w", c.cfg.PodNSName, err) + } + + replicaCount, err := getReplicas(replicaSet) if err != nil { return Data{}, fmt.Errorf("failed to collect NGF replica count: %w", err) } + deploymentID, err := getDeploymentID(replicaSet) + if err != nil { + return Data{}, fmt.Errorf("failed to get NGF deploymentID: %w", err) + } + var clusterID string if clusterID, err = CollectClusterID(ctx, c.cfg.K8sClientReader); err != nil { return Data{}, fmt.Errorf("failed to collect clusterID: %w", err) @@ -118,10 +129,11 @@ func (c DataCollectorImpl) Collect(ctx context.Context) (Data, error) { Name: "NGF", Version: c.cfg.Version, }, - NGFReplicaCount: ngfReplicaCount, + NGFReplicaCount: replicaCount, ClusterID: clusterID, ImageSource: c.cfg.ImageSource, Arch: runtime.GOARCH, + DeploymentID: deploymentID, } return data, nil @@ -175,23 +187,27 @@ func collectGraphResourceCount( return ngfResourceCounts, nil } -func collectNGFReplicaCount(ctx context.Context, k8sClient client.Reader, podNSName types.NamespacedName) (int, error) { +func getPodReplicaSet( + ctx context.Context, + k8sClient client.Reader, + podNSName types.NamespacedName, +) (*appsv1.ReplicaSet, error) { var pod v1.Pod if err := k8sClient.Get( ctx, types.NamespacedName{Namespace: podNSName.Namespace, Name: podNSName.Name}, &pod, ); err != nil { - return 0, fmt.Errorf("failed to get NGF Pod: %w", err) + return nil, fmt.Errorf("failed to get NGF Pod: %w", err) } podOwnerRefs := pod.GetOwnerReferences() if len(podOwnerRefs) != 1 { - return 0, fmt.Errorf("expected one owner reference of the NGF Pod, got %d", len(podOwnerRefs)) + return nil, fmt.Errorf("expected one owner reference of the NGF Pod, got %d", len(podOwnerRefs)) } if podOwnerRefs[0].Kind != "ReplicaSet" { - return 0, fmt.Errorf("expected pod owner reference to be ReplicaSet, got %s", podOwnerRefs[0].Kind) + return nil, fmt.Errorf("expected pod owner reference to be ReplicaSet, got %s", podOwnerRefs[0].Kind) } var replicaSet appsv1.ReplicaSet @@ -200,9 +216,13 @@ func collectNGFReplicaCount(ctx context.Context, k8sClient client.Reader, podNSN types.NamespacedName{Namespace: podNSName.Namespace, Name: podOwnerRefs[0].Name}, &replicaSet, ); err != nil { - return 0, fmt.Errorf("failed to get NGF Pod's ReplicaSet: %w", err) + return nil, fmt.Errorf("failed to get NGF Pod's ReplicaSet: %w", err) } + return &replicaSet, nil +} + +func getReplicas(replicaSet *appsv1.ReplicaSet) (int, error) { if replicaSet.Spec.Replicas == nil { return 0, errors.New("replica set replicas was nil") } @@ -210,6 +230,23 @@ func collectNGFReplicaCount(ctx context.Context, k8sClient client.Reader, podNSN return int(*replicaSet.Spec.Replicas), nil } +func getDeploymentID(replicaSet *appsv1.ReplicaSet) (string, error) { + replicaOwnerRefs := replicaSet.GetOwnerReferences() + if len(replicaOwnerRefs) != 1 { + return "", fmt.Errorf("expected one owner reference of the NGF ReplicaSet, got %d", len(replicaOwnerRefs)) + } + + if replicaOwnerRefs[0].Kind != "Deployment" { + return "", fmt.Errorf("expected replicaSet owner reference to be Deployment, got %s", replicaOwnerRefs[0].Kind) + } + + if replicaOwnerRefs[0].UID == "" { + return "", fmt.Errorf("expected replicaSet owner reference to have a UID") + } + + return string(replicaOwnerRefs[0].UID), nil +} + // CollectClusterID gets the UID of the kube-system namespace. func CollectClusterID(ctx context.Context, k8sClient client.Reader) (string, error) { key := types.NamespacedName{ diff --git a/internal/mode/static/telemetry/collector_test.go b/internal/mode/static/telemetry/collector_test.go index 4b8c0991e8..3a8da82260 100644 --- a/internal/mode/static/telemetry/collector_test.go +++ b/internal/mode/static/telemetry/collector_test.go @@ -102,6 +102,16 @@ var _ = Describe("Collector", Ordered, func() { Spec: appsv1.ReplicaSetSpec{ Replicas: &replicas, }, + ObjectMeta: metav1.ObjectMeta{ + Name: "replica", + OwnerReferences: []metav1.OwnerReference{ + { + Kind: "Deployment", + Name: "Deployment1", + UID: "test-uid-replicaSet", + }, + }, + }, } podNSName = types.NamespacedName{ @@ -126,6 +136,7 @@ var _ = Describe("Collector", Ordered, func() { ClusterID: string(kubeNamespace.GetUID()), ImageSource: "local", Arch: runtime.GOARCH, + DeploymentID: string(ngfReplicaSet.ObjectMeta.OwnerReferences[0].UID), } k8sClientReader = &eventsfakes.FakeReader{} @@ -547,6 +558,7 @@ var _ = Describe("Collector", Ordered, func() { { Kind: "Deployment", Name: "deployment1", + UID: "replica-uid", }, }, }, @@ -588,4 +600,70 @@ var _ = Describe("Collector", Ordered, func() { }) }) }) + + Describe("DeploymentID collector", func() { + When("collecting deploymentID", func() { + When("it encounters an error while collecting data", func() { + It("should error if the replicaSet's owner reference is nil", func() { + replicas := int32(1) + k8sClientReader.GetCalls(mergeGetCallsWithBase(createGetCallsFunc( + &appsv1.ReplicaSet{ + Spec: appsv1.ReplicaSetSpec{ + Replicas: &replicas, + }, + }, + ))) + + expectedErr := errors.New("expected one owner reference of the NGF ReplicaSet, got 0") + _, err := dataCollector.Collect(ctx) + Expect(err).To(MatchError(expectedErr)) + }) + + It("should error if the replicaSet's owner reference kind is not deployment", func() { + replicas := int32(1) + k8sClientReader.GetCalls(mergeGetCallsWithBase(createGetCallsFunc( + &appsv1.ReplicaSet{ + Spec: appsv1.ReplicaSetSpec{ + Replicas: &replicas, + }, + ObjectMeta: metav1.ObjectMeta{ + OwnerReferences: []metav1.OwnerReference{ + { + Name: "replica", + Kind: "ReplicaSet", + }, + }, + }, + }, + ))) + + expectedErr := errors.New("expected replicaSet owner reference to be Deployment, got ReplicaSet") + _, err := dataCollector.Collect(ctx) + Expect(err).To(MatchError(expectedErr)) + }) + It("should error if the replicaSet's owner reference has empty UID", func() { + replicas := int32(1) + k8sClientReader.GetCalls(mergeGetCallsWithBase(createGetCallsFunc( + &appsv1.ReplicaSet{ + Spec: appsv1.ReplicaSetSpec{ + Replicas: &replicas, + }, + ObjectMeta: metav1.ObjectMeta{ + OwnerReferences: []metav1.OwnerReference{ + { + Name: "replica", + Kind: "Deployment", + }, + }, + }, + }, + ))) + + expectedErr := errors.New("expected replicaSet owner reference to have a UID") + _, err := dataCollector.Collect(ctx) + Expect(err).To(MatchError(expectedErr)) + }) + }) + }) + }) })