Skip to content

Commit 1d643ee

Browse files
committed
feat: Add label for root parent object
Signed-off-by: Scott Fleener <[email protected]>
1 parent 50801b2 commit 1d643ee

File tree

9 files changed

+155
-58
lines changed

9 files changed

+155
-58
lines changed

controller/api/destination/watcher/endpoints_watcher.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1145,10 +1145,7 @@ func (pp *portPublisher) newPodRefAddress(
11451145
if err != nil {
11461146
return Address{}, PodID{}, fmt.Errorf("unable to fetch pod %v: %w", id, err)
11471147
}
1148-
ownerKind, ownerName, err := pp.metadataAPI.GetOwnerKindAndName(context.Background(), pod, false)
1149-
if err != nil {
1150-
return Address{}, PodID{}, err
1151-
}
1148+
ownerKind, ownerName := pp.metadataAPI.GetOwnerKindAndName(context.Background(), pod, false)
11521149
addr := Address{
11531150
IP: endpointIP,
11541151
Port: endpointPort,

controller/api/destination/watcher/workload_watcher.go

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -695,13 +695,9 @@ func (wp *workloadPublisher) updatePod(pod *corev1.Pod) {
695695

696696
// Fill in ownership.
697697
if wp.addr.Pod != nil {
698-
ownerKind, ownerName, err := wp.metadataAPI.GetOwnerKindAndName(context.Background(), wp.addr.Pod, true)
699-
if err != nil {
700-
wp.log.Errorf("Error getting pod owner for pod %s: %q", wp.addr.Pod.GetName(), err)
701-
} else {
702-
wp.addr.OwnerKind = ownerKind
703-
wp.addr.OwnerName = ownerName
704-
}
698+
ownerKind, ownerName := wp.metadataAPI.GetOwnerKindAndName(context.Background(), wp.addr.Pod, true)
699+
wp.addr.OwnerKind = ownerKind
700+
wp.addr.OwnerName = ownerName
705701
}
706702

707703
// Compute opaque protocol.

controller/k8s/api_test.go

Lines changed: 47 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ func newMockAPI(useInformer bool, res resources) (
5959

6060
if useInformer {
6161
api.Sync(nil)
62-
metadataAPI.Sync(nil)
6362
}
63+
metadataAPI.Sync(nil)
6464

6565
return api, metadataAPI, k8sResults, nil
6666
}
@@ -1057,12 +1057,16 @@ func TestGetOwnerKindAndName(t *testing.T) {
10571057
for i, tt := range []struct {
10581058
resources
10591059

1060-
expectedOwnerKind string
1061-
expectedOwnerName string
1060+
expectedOwnerKind string
1061+
expectedOwnerName string
1062+
expectedRootOwnerKind string
1063+
expectedRootOwnerName string
10621064
}{
10631065
{
1064-
expectedOwnerKind: "deployment",
1065-
expectedOwnerName: "t2",
1066+
expectedOwnerKind: "deployment",
1067+
expectedOwnerName: "t2",
1068+
expectedRootOwnerKind: "Deployment",
1069+
expectedRootOwnerName: "t2",
10661070
resources: resources{
10671071
results: []string{`
10681072
apiVersion: v1
@@ -1084,13 +1088,14 @@ metadata:
10841088
ownerReferences:
10851089
- apiVersion: apps/v1
10861090
kind: Deployment
1087-
name: t2`,
1088-
},
1091+
name: t2`},
10891092
},
10901093
},
10911094
{
1092-
expectedOwnerKind: "replicaset",
1093-
expectedOwnerName: "t1-b4f55d87f",
1095+
expectedOwnerKind: "replicaset",
1096+
expectedOwnerName: "t1-b4f55d87f",
1097+
expectedRootOwnerKind: "ReplicaSet",
1098+
expectedRootOwnerName: "t1-b4f55d87f",
10941099
resources: resources{
10951100
results: []string{`
10961101
apiVersion: v1
@@ -1106,8 +1111,10 @@ metadata:
11061111
},
11071112
},
11081113
{
1109-
expectedOwnerKind: "job",
1110-
expectedOwnerName: "slow-cooker",
1114+
expectedOwnerKind: "job",
1115+
expectedOwnerName: "slow-cooker",
1116+
expectedRootOwnerKind: "Job",
1117+
expectedRootOwnerName: "slow-cooker",
11111118
resources: resources{
11121119
results: []string{`
11131120
apiVersion: v1
@@ -1123,8 +1130,10 @@ metadata:
11231130
},
11241131
},
11251132
{
1126-
expectedOwnerKind: "replicationcontroller",
1127-
expectedOwnerName: "web",
1133+
expectedOwnerKind: "replicationcontroller",
1134+
expectedOwnerName: "web",
1135+
expectedRootOwnerKind: "ReplicationController",
1136+
expectedRootOwnerName: "web",
11281137
resources: resources{
11291138
results: []string{`
11301139
apiVersion: v1
@@ -1140,8 +1149,10 @@ metadata:
11401149
},
11411150
},
11421151
{
1143-
expectedOwnerKind: "pod",
1144-
expectedOwnerName: "vote-bot",
1152+
expectedOwnerKind: "pod",
1153+
expectedOwnerName: "vote-bot",
1154+
expectedRootOwnerKind: "Pod",
1155+
expectedRootOwnerName: "vote-bot",
11451156
resources: resources{
11461157
results: []string{`
11471158
apiVersion: v1
@@ -1153,8 +1164,10 @@ metadata:
11531164
},
11541165
},
11551166
{
1156-
expectedOwnerKind: "cronjob",
1157-
expectedOwnerName: "my-cronjob",
1167+
expectedOwnerKind: "cronjob",
1168+
expectedOwnerName: "my-cronjob",
1169+
expectedRootOwnerKind: "CronJob",
1170+
expectedRootOwnerName: "my-cronjob",
11581171
resources: resources{
11591172
results: []string{`
11601173
apiVersion: v1
@@ -1176,13 +1189,14 @@ metadata:
11761189
ownerReferences:
11771190
- apiVersion: batch/v1
11781191
kind: CronJob
1179-
name: my-cronjob`,
1180-
},
1192+
name: my-cronjob`},
11811193
},
11821194
},
11831195
{
1184-
expectedOwnerKind: "replicaset",
1185-
expectedOwnerName: "invalid-rs-parent-2abdffa",
1196+
expectedOwnerKind: "replicaset",
1197+
expectedOwnerName: "invalid-rs-parent-2abdffa",
1198+
expectedRootOwnerKind: "InvalidParentKind",
1199+
expectedRootOwnerName: "invalid-parent",
11861200
resources: resources{
11871201
results: []string{`
11881202
apiVersion: v1
@@ -1204,8 +1218,7 @@ metadata:
12041218
ownerReferences:
12051219
- apiVersion: invalidParent/v1
12061220
kind: InvalidParentKind
1207-
name: invalid-parent`,
1208-
},
1221+
name: invalid-parent`},
12091222
},
12101223
},
12111224
} {
@@ -1232,10 +1245,7 @@ metadata:
12321245
t.Fatalf("Expected name to be [%s], got [%s]", tt.expectedOwnerName, ownerName)
12331246
}
12341247

1235-
ownerKind, ownerName, err = metadataAPI.GetOwnerKindAndName(context.Background(), pod, retry)
1236-
if err != nil {
1237-
t.Fatalf("Unexpected error: %s", err)
1238-
}
1248+
ownerKind, ownerName = metadataAPI.GetOwnerKindAndName(context.Background(), pod, retry)
12391249

12401250
if ownerKind != tt.expectedOwnerKind {
12411251
t.Fatalf("Expected kind to be [%s], got [%s]", tt.expectedOwnerKind, ownerKind)
@@ -1244,6 +1254,16 @@ metadata:
12441254
if ownerName != tt.expectedOwnerName {
12451255
t.Fatalf("Expected name to be [%s], got [%s]", tt.expectedOwnerName, ownerName)
12461256
}
1257+
1258+
tm, om := metadataAPI.GetRootOwnerKindAndName(context.Background(), &pod.TypeMeta, &pod.ObjectMeta)
1259+
1260+
if tm.Kind != tt.expectedRootOwnerKind {
1261+
t.Fatalf("Expected root kind to be [%s], got [%s]", tt.expectedRootOwnerKind, tm.Kind)
1262+
}
1263+
1264+
if om.Name != tt.expectedRootOwnerName {
1265+
t.Fatalf("Expected root name to be [%s], got [%s]", tt.expectedRootOwnerName, om.Name)
1266+
}
12471267
})
12481268
}
12491269
}

controller/k8s/metadata_api.go

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1616
"k8s.io/apimachinery/pkg/labels"
1717
"k8s.io/apimachinery/pkg/runtime"
18+
"k8s.io/apimachinery/pkg/runtime/schema"
1819
"k8s.io/client-go/informers"
1920
"k8s.io/client-go/metadata"
2021
"k8s.io/client-go/metadata/metadatainformer"
@@ -213,14 +214,14 @@ func (api *MetadataAPI) GetByNamespaceFiltered(
213214
// Kubernetes singular resource type (e.g. deployment, daemonset, job, etc.).
214215
// If retry is true, when the shared informer cache doesn't return anything we
215216
// try again with a direct Kubernetes API call.
216-
func (api *MetadataAPI) GetOwnerKindAndName(ctx context.Context, pod *corev1.Pod, retry bool) (string, string, error) {
217+
func (api *MetadataAPI) GetOwnerKindAndName(ctx context.Context, pod *corev1.Pod, retry bool) (string, string) {
217218
ownerRefs := pod.GetOwnerReferences()
218219
if len(ownerRefs) == 0 {
219220
// pod without a parent
220-
return "pod", pod.Name, nil
221+
return "pod", pod.Name
221222
} else if len(ownerRefs) > 1 {
222223
log.Debugf("unexpected owner reference count (%d): %+v", len(ownerRefs), ownerRefs)
223-
return "pod", pod.Name, nil
224+
return "pod", pod.Name
224225
}
225226

226227
parent := ownerRefs[0]
@@ -258,18 +259,69 @@ func (api *MetadataAPI) GetOwnerKindAndName(ctx context.Context, pod *corev1.Pod
258259
}
259260

260261
if rsObj == nil || !isValidRSParent(rsObj) {
261-
return strings.ToLower(parent.Kind), parent.Name, nil
262+
return strings.ToLower(parent.Kind), parent.Name
262263
}
263264
parentObj = rsObj
264265
default:
265-
return strings.ToLower(parent.Kind), parent.Name, nil
266+
return strings.ToLower(parent.Kind), parent.Name
266267
}
267268

268269
if err == nil && len(parentObj.GetOwnerReferences()) == 1 {
269270
grandParent := parentObj.GetOwnerReferences()[0]
270-
return strings.ToLower(grandParent.Kind), grandParent.Name, nil
271+
return strings.ToLower(grandParent.Kind), grandParent.Name
271272
}
272-
return strings.ToLower(parent.Kind), parent.Name, nil
273+
return strings.ToLower(parent.Kind), parent.Name
274+
}
275+
276+
// GetRootOwnerKindAndName returns the resource's owner's type and metadata, using owner
277+
// references from the Kubernetes API. Parent refs are recursively traversed to find the
278+
// root parent resource, at least as far as the controller has permissions to query.
279+
// This will attempt to lookup resources through the shared informer cache where possible,
280+
// but may fall back to direct Kubernetes API calls where necessary.
281+
func (api *MetadataAPI) GetRootOwnerKindAndName(ctx context.Context, tm *metav1.TypeMeta, om *metav1.ObjectMeta) (*metav1.TypeMeta, *metav1.ObjectMeta) {
282+
ownerRefs := om.OwnerReferences
283+
if len(ownerRefs) == 0 {
284+
// resource without a parent
285+
log.Debugf("Found root owner ref (%s)", om)
286+
return tm, om
287+
} else if len(ownerRefs) > 1 {
288+
log.Debugf("unexpected owner reference count (%d): %+v", len(ownerRefs), ownerRefs)
289+
return tm, om
290+
}
291+
292+
parentRef := ownerRefs[0]
293+
// The set of resources that we look up in the indexer are fairly niche. They all must be able to:
294+
// - be a parent of another resource, usually a Pod
295+
// - have a parent resource themselves
296+
// Currently, this is limited to Jobs (parented by CronJobs) and ReplicaSets (parented by
297+
// Deployments, StatefulSets, argo Rollouts, etc.). This list may change in the future, but
298+
// is sufficient for now.
299+
switch parentRef.Kind {
300+
case "Job":
301+
parent, err := api.getByNamespace(Job, om.Namespace, parentRef.Name)
302+
if err == nil {
303+
return api.GetRootOwnerKindAndName(ctx, &parent.TypeMeta, &parent.ObjectMeta)
304+
}
305+
log.Warnf("failed to retrieve job from indexer %s/%s: %s", om.Namespace, parentRef.Name, err)
306+
case "ReplicaSet":
307+
parent, err := api.getByNamespace(RS, om.Namespace, parentRef.Name)
308+
if err == nil {
309+
return api.GetRootOwnerKindAndName(ctx, &parent.TypeMeta, &parent.ObjectMeta)
310+
}
311+
log.Warnf("failed to retrieve replicaset from indexer %s/%s: %s", om.Namespace, parentRef.Name, err)
312+
}
313+
314+
resource := schema.FromAPIVersionAndKind(parentRef.APIVersion, parentRef.Kind).
315+
GroupVersion().
316+
WithResource(parentRef.Kind)
317+
parent, err := api.client.Resource(resource).
318+
Namespace(om.Namespace).
319+
Get(ctx, parentRef.Name, metav1.GetOptions{})
320+
if err != nil {
321+
log.Warnf("failed to retrieve resource from direct API call %s/%s/%s: %s", schema.FromAPIVersionAndKind(parentRef.APIVersion, parentRef.Kind), om.Namespace, parentRef.Name, err)
322+
return &metav1.TypeMeta{Kind: parentRef.Kind, APIVersion: parentRef.APIVersion}, &metav1.ObjectMeta{Name: parentRef.Name, Namespace: om.Namespace}
323+
}
324+
return api.GetRootOwnerKindAndName(ctx, &parent.TypeMeta, &parent.ObjectMeta)
273325
}
274326

275327
func (api *MetadataAPI) addInformer(res APIResource, informerLabels prometheus.Labels) error {

controller/k8s/test_helper.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,10 @@ func NewFakeClusterScopedAPI(clientSet kubernetes.Interface, l5dClientSet l5dcrd
7777
// NewFakeMetadataAPI provides a mock Kubernetes API for testing.
7878
func NewFakeMetadataAPI(configs []string) (*MetadataAPI, error) {
7979
sch := runtime.NewScheme()
80-
metav1.AddMetaToScheme(sch)
80+
err := metav1.AddMetaToScheme(sch)
81+
if err != nil {
82+
return nil, err
83+
}
8184

8285
var objs []runtime.Object
8386
for _, config := range configs {

controller/proxy-injector/webhook.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ func Inject(linkerdNamespace string, overrider inject.ValueOverrider) webhook.Ha
5757
}
5858
resourceConfig := inject.NewResourceConfig(valuesConfig, inject.OriginWebhook, linkerdNamespace).
5959
WithOwnerRetriever(ownerRetriever(ctx, api, request.Namespace)).
60+
WithRootOwnerRetriever(rootOwnerRetriever(ctx, api, request.Namespace)).
6061
WithNsAnnotations(ns.GetAnnotations()).
6162
WithKind(request.Kind.Kind)
6263

@@ -217,8 +218,15 @@ func Inject(linkerdNamespace string, overrider inject.ValueOverrider) webhook.Ha
217218
}
218219

219220
func ownerRetriever(ctx context.Context, api *k8s.MetadataAPI, ns string) inject.OwnerRetrieverFunc {
220-
return func(p *v1.Pod) (string, string, error) {
221+
return func(p *v1.Pod) (string, string) {
221222
p.SetNamespace(ns)
222223
return api.GetOwnerKindAndName(ctx, p, true)
223224
}
224225
}
226+
227+
func rootOwnerRetriever(ctx context.Context, api *k8s.MetadataAPI, ns string) inject.RootOwnerRetrieverFunc {
228+
return func(tm *metav1.TypeMeta, om *metav1.ObjectMeta) (*metav1.TypeMeta, *metav1.ObjectMeta) {
229+
om.SetNamespace(ns)
230+
return api.GetRootOwnerKindAndName(ctx, tm, om)
231+
}
232+
}

controller/proxy-injector/webhook_test.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,8 @@ func TestGetAnnotationPatch(t *testing.T) {
364364
fakeReq := getFakeServiceReq(service)
365365
fullConf := testCase.conf.
366366
WithKind(fakeReq.Kind.Kind).
367-
WithOwnerRetriever(ownerRetrieverFake)
367+
WithOwnerRetriever(ownerRetrieverFake).
368+
WithRootOwnerRetriever(rootOwnerRetrieverFake)
368369
_, err = fullConf.ParseMetaAndYAML(fakeReq.Object.Raw)
369370
if err != nil {
370371
t.Fatal(err)
@@ -408,8 +409,12 @@ func getFakeServiceReq(b []byte) *admissionv1beta1.AdmissionRequest {
408409
}
409410
}
410411

411-
func ownerRetrieverFake(p *corev1.Pod) (string, string, error) {
412-
return pkgK8s.Deployment, "owner-deployment", nil
412+
func ownerRetrieverFake(p *corev1.Pod) (string, string) {
413+
return pkgK8s.Deployment, "owner-deployment"
414+
}
415+
416+
func rootOwnerRetrieverFake(tm *metav1.TypeMeta, om *metav1.ObjectMeta) (*metav1.TypeMeta, *metav1.ObjectMeta) {
417+
return &metav1.TypeMeta{Kind: "Deployment", APIVersion: "apps/v1"}, &metav1.ObjectMeta{Name: "owner-deployment"}
413418
}
414419

415420
func loadPatch(factory *fake.Factory, t *testing.T, name string) ([]byte, unmarshalledPatch) {

0 commit comments

Comments
 (0)