...

Source file src/k8s.io/kubernetes/pkg/controller/garbagecollector/dump_test.go

Documentation: k8s.io/kubernetes/pkg/controller/garbagecollector

     1  /*
     2  Copyright 2018 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package garbagecollector
    18  
    19  import (
    20  	"bytes"
    21  	"os"
    22  	"path/filepath"
    23  	"testing"
    24  
    25  	"github.com/google/go-cmp/cmp"
    26  
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"k8s.io/apimachinery/pkg/types"
    29  	"k8s.io/apimachinery/pkg/util/dump"
    30  )
    31  
    32  var (
    33  	alphaNode = func() *node {
    34  		return &node{
    35  			identity: objectReference{
    36  				OwnerReference: metav1.OwnerReference{
    37  					UID: types.UID("alpha"),
    38  				},
    39  			},
    40  			owners: []metav1.OwnerReference{
    41  				{UID: types.UID("bravo")},
    42  				{UID: types.UID("charlie")},
    43  			},
    44  		}
    45  	}
    46  	bravoNode = func() *node {
    47  		return &node{
    48  			identity: objectReference{
    49  				OwnerReference: metav1.OwnerReference{
    50  					UID: types.UID("bravo"),
    51  				},
    52  			},
    53  			dependents: map[*node]struct{}{
    54  				alphaNode(): {},
    55  			},
    56  		}
    57  	}
    58  	charlieNode = func() *node {
    59  		return &node{
    60  			identity: objectReference{
    61  				OwnerReference: metav1.OwnerReference{
    62  					UID: types.UID("charlie"),
    63  				},
    64  			},
    65  			dependents: map[*node]struct{}{
    66  				alphaNode(): {},
    67  			},
    68  		}
    69  	}
    70  	deltaNode = func() *node {
    71  		return &node{
    72  			identity: objectReference{
    73  				OwnerReference: metav1.OwnerReference{
    74  					UID: types.UID("delta"),
    75  				},
    76  			},
    77  			owners: []metav1.OwnerReference{
    78  				{UID: types.UID("foxtrot")},
    79  			},
    80  		}
    81  	}
    82  	echoNode = func() *node {
    83  		return &node{
    84  			identity: objectReference{
    85  				OwnerReference: metav1.OwnerReference{
    86  					UID: types.UID("echo"),
    87  				},
    88  			},
    89  		}
    90  	}
    91  	foxtrotNode = func() *node {
    92  		return &node{
    93  			identity: objectReference{
    94  				OwnerReference: metav1.OwnerReference{
    95  					UID: types.UID("foxtrot"),
    96  				},
    97  			},
    98  			owners: []metav1.OwnerReference{
    99  				{UID: types.UID("golf")},
   100  			},
   101  			dependents: map[*node]struct{}{
   102  				deltaNode(): {},
   103  			},
   104  		}
   105  	}
   106  	golfNode = func() *node {
   107  		return &node{
   108  			identity: objectReference{
   109  				OwnerReference: metav1.OwnerReference{
   110  					UID: types.UID("golf"),
   111  				},
   112  			},
   113  			dependents: map[*node]struct{}{
   114  				foxtrotNode(): {},
   115  			},
   116  		}
   117  	}
   118  )
   119  
   120  func TestToDOTGraph(t *testing.T) {
   121  	tests := []struct {
   122  		name        string
   123  		uidToNode   map[types.UID]*node
   124  		expectNodes []*dotVertex
   125  		expectEdges []dotEdge
   126  	}{
   127  		{
   128  			name: "simple",
   129  			uidToNode: map[types.UID]*node{
   130  				types.UID("alpha"):   alphaNode(),
   131  				types.UID("bravo"):   bravoNode(),
   132  				types.UID("charlie"): charlieNode(),
   133  			},
   134  			expectNodes: []*dotVertex{
   135  				NewDOTVertex(alphaNode()),
   136  				NewDOTVertex(bravoNode()),
   137  				NewDOTVertex(charlieNode()),
   138  			},
   139  			expectEdges: []dotEdge{
   140  				{F: types.UID("alpha"), T: types.UID("bravo")},
   141  				{F: types.UID("alpha"), T: types.UID("charlie")},
   142  			},
   143  		},
   144  		{
   145  			name: "missing", // synthetic vertex created
   146  			uidToNode: map[types.UID]*node{
   147  				types.UID("alpha"):   alphaNode(),
   148  				types.UID("charlie"): charlieNode(),
   149  			},
   150  			expectNodes: []*dotVertex{
   151  				NewDOTVertex(alphaNode()),
   152  				NewDOTVertex(bravoNode()),
   153  				NewDOTVertex(charlieNode()),
   154  			},
   155  			expectEdges: []dotEdge{
   156  				{F: types.UID("alpha"), T: types.UID("bravo")},
   157  				{F: types.UID("alpha"), T: types.UID("charlie")},
   158  			},
   159  		},
   160  		{
   161  			name: "drop-no-ref",
   162  			uidToNode: map[types.UID]*node{
   163  				types.UID("alpha"):   alphaNode(),
   164  				types.UID("bravo"):   bravoNode(),
   165  				types.UID("charlie"): charlieNode(),
   166  				types.UID("echo"):    echoNode(),
   167  			},
   168  			expectNodes: []*dotVertex{
   169  				NewDOTVertex(alphaNode()),
   170  				NewDOTVertex(bravoNode()),
   171  				NewDOTVertex(charlieNode()),
   172  			},
   173  			expectEdges: []dotEdge{
   174  				{F: types.UID("alpha"), T: types.UID("bravo")},
   175  				{F: types.UID("alpha"), T: types.UID("charlie")},
   176  			},
   177  		},
   178  		{
   179  			name: "two-chains",
   180  			uidToNode: map[types.UID]*node{
   181  				types.UID("alpha"):   alphaNode(),
   182  				types.UID("bravo"):   bravoNode(),
   183  				types.UID("charlie"): charlieNode(),
   184  				types.UID("delta"):   deltaNode(),
   185  				types.UID("foxtrot"): foxtrotNode(),
   186  				types.UID("golf"):    golfNode(),
   187  			},
   188  			expectNodes: []*dotVertex{
   189  				NewDOTVertex(alphaNode()),
   190  				NewDOTVertex(bravoNode()),
   191  				NewDOTVertex(charlieNode()),
   192  				NewDOTVertex(deltaNode()),
   193  				NewDOTVertex(foxtrotNode()),
   194  				NewDOTVertex(golfNode()),
   195  			},
   196  			expectEdges: []dotEdge{
   197  				{F: types.UID("alpha"), T: types.UID("bravo")},
   198  				{F: types.UID("alpha"), T: types.UID("charlie")},
   199  				{F: types.UID("delta"), T: types.UID("foxtrot")},
   200  				{F: types.UID("foxtrot"), T: types.UID("golf")},
   201  			},
   202  		},
   203  	}
   204  
   205  	for _, test := range tests {
   206  		t.Run(test.name, func(t *testing.T) {
   207  			actualNodes, actualEdges := toDOTNodesAndEdges(test.uidToNode)
   208  			compareGraphs(test.expectNodes, actualNodes, test.expectEdges, actualEdges, t)
   209  		})
   210  	}
   211  }
   212  
   213  func TestToDOTGraphObj(t *testing.T) {
   214  	tests := []struct {
   215  		name        string
   216  		uidToNode   map[types.UID]*node
   217  		uids        []types.UID
   218  		expectNodes []*dotVertex
   219  		expectEdges []dotEdge
   220  	}{
   221  		{
   222  			name: "simple",
   223  			uidToNode: map[types.UID]*node{
   224  				types.UID("alpha"):   alphaNode(),
   225  				types.UID("bravo"):   bravoNode(),
   226  				types.UID("charlie"): charlieNode(),
   227  			},
   228  			uids: []types.UID{types.UID("bravo")},
   229  			expectNodes: []*dotVertex{
   230  				NewDOTVertex(alphaNode()),
   231  				NewDOTVertex(bravoNode()),
   232  				NewDOTVertex(charlieNode()),
   233  			},
   234  			expectEdges: []dotEdge{
   235  				{F: types.UID("alpha"), T: types.UID("bravo")},
   236  				{F: types.UID("alpha"), T: types.UID("charlie")},
   237  			},
   238  		},
   239  		{
   240  			name: "missing", // synthetic vertex created
   241  			uidToNode: map[types.UID]*node{
   242  				types.UID("alpha"):   alphaNode(),
   243  				types.UID("charlie"): charlieNode(),
   244  			},
   245  			uids:        []types.UID{types.UID("bravo")},
   246  			expectNodes: []*dotVertex{},
   247  			expectEdges: []dotEdge{},
   248  		},
   249  		{
   250  			name: "drop-no-ref",
   251  			uidToNode: map[types.UID]*node{
   252  				types.UID("alpha"):   alphaNode(),
   253  				types.UID("bravo"):   bravoNode(),
   254  				types.UID("charlie"): charlieNode(),
   255  				types.UID("echo"):    echoNode(),
   256  			},
   257  			uids:        []types.UID{types.UID("echo")},
   258  			expectNodes: []*dotVertex{},
   259  			expectEdges: []dotEdge{},
   260  		},
   261  		{
   262  			name: "two-chains-from-owner",
   263  			uidToNode: map[types.UID]*node{
   264  				types.UID("alpha"):   alphaNode(),
   265  				types.UID("bravo"):   bravoNode(),
   266  				types.UID("charlie"): charlieNode(),
   267  				types.UID("delta"):   deltaNode(),
   268  				types.UID("foxtrot"): foxtrotNode(),
   269  				types.UID("golf"):    golfNode(),
   270  			},
   271  			uids: []types.UID{types.UID("golf")},
   272  			expectNodes: []*dotVertex{
   273  				NewDOTVertex(deltaNode()),
   274  				NewDOTVertex(foxtrotNode()),
   275  				NewDOTVertex(golfNode()),
   276  			},
   277  			expectEdges: []dotEdge{
   278  				{F: types.UID("delta"), T: types.UID("foxtrot")},
   279  				{F: types.UID("foxtrot"), T: types.UID("golf")},
   280  			},
   281  		},
   282  		{
   283  			name: "two-chains-from-child",
   284  			uidToNode: map[types.UID]*node{
   285  				types.UID("alpha"):   alphaNode(),
   286  				types.UID("bravo"):   bravoNode(),
   287  				types.UID("charlie"): charlieNode(),
   288  				types.UID("delta"):   deltaNode(),
   289  				types.UID("foxtrot"): foxtrotNode(),
   290  				types.UID("golf"):    golfNode(),
   291  			},
   292  			uids: []types.UID{types.UID("delta")},
   293  			expectNodes: []*dotVertex{
   294  				NewDOTVertex(deltaNode()),
   295  				NewDOTVertex(foxtrotNode()),
   296  				NewDOTVertex(golfNode()),
   297  			},
   298  			expectEdges: []dotEdge{
   299  				{F: types.UID("delta"), T: types.UID("foxtrot")},
   300  				{F: types.UID("foxtrot"), T: types.UID("golf")},
   301  			},
   302  		},
   303  		{
   304  			name: "two-chains-choose-both",
   305  			uidToNode: map[types.UID]*node{
   306  				types.UID("alpha"):   alphaNode(),
   307  				types.UID("bravo"):   bravoNode(),
   308  				types.UID("charlie"): charlieNode(),
   309  				types.UID("delta"):   deltaNode(),
   310  				types.UID("foxtrot"): foxtrotNode(),
   311  				types.UID("golf"):    golfNode(),
   312  			},
   313  			uids: []types.UID{types.UID("delta"), types.UID("charlie")},
   314  			expectNodes: []*dotVertex{
   315  				NewDOTVertex(alphaNode()),
   316  				NewDOTVertex(bravoNode()),
   317  				NewDOTVertex(charlieNode()),
   318  				NewDOTVertex(deltaNode()),
   319  				NewDOTVertex(foxtrotNode()),
   320  				NewDOTVertex(golfNode()),
   321  			},
   322  			expectEdges: []dotEdge{
   323  				{F: types.UID("alpha"), T: types.UID("bravo")},
   324  				{F: types.UID("alpha"), T: types.UID("charlie")},
   325  				{F: types.UID("delta"), T: types.UID("foxtrot")},
   326  				{F: types.UID("foxtrot"), T: types.UID("golf")},
   327  			},
   328  		},
   329  	}
   330  
   331  	for _, test := range tests {
   332  		t.Run(test.name, func(t *testing.T) {
   333  			actualNodes, actualEdges := toDOTNodesAndEdgesForObj(test.uidToNode, test.uids...)
   334  			compareGraphs(test.expectNodes, actualNodes, test.expectEdges, actualEdges, t)
   335  		})
   336  	}
   337  }
   338  
   339  func compareGraphs(expectedNodes, actualNodes []*dotVertex, expectedEdges, actualEdges []dotEdge, t *testing.T) {
   340  	if len(expectedNodes) != len(actualNodes) {
   341  		t.Fatal(dump.Pretty(actualNodes))
   342  	}
   343  	for i := range expectedNodes {
   344  		currExpected := expectedNodes[i]
   345  		currActual := actualNodes[i]
   346  		if currExpected.uid != currActual.uid {
   347  			t.Errorf("expected %v, got %v", dump.Pretty(currExpected), dump.Pretty(currActual))
   348  		}
   349  	}
   350  	if len(expectedEdges) != len(actualEdges) {
   351  		t.Fatal(dump.Pretty(actualEdges))
   352  	}
   353  	for i := range expectedEdges {
   354  		currExpected := expectedEdges[i]
   355  		currActual := actualEdges[i]
   356  		if currExpected != currActual {
   357  			t.Errorf("expected %v, got %v", dump.Pretty(currExpected), dump.Pretty(currActual))
   358  		}
   359  	}
   360  }
   361  
   362  func TestMarshalDOT(t *testing.T) {
   363  	ref1 := objectReference{
   364  		OwnerReference: metav1.OwnerReference{
   365  			UID:        types.UID("ref1-[]\"\\Iñtërnâtiônàlizætiøn,🐹"),
   366  			Name:       "ref1name-Iñtërnâtiônàlizætiøn,🐹",
   367  			Kind:       "ref1kind-Iñtërnâtiônàlizætiøn,🐹",
   368  			APIVersion: "ref1group/version",
   369  		},
   370  		Namespace: "ref1ns",
   371  	}
   372  	ref2 := objectReference{
   373  		OwnerReference: metav1.OwnerReference{
   374  			UID:        types.UID("ref2-"),
   375  			Name:       "ref2name-",
   376  			Kind:       "ref2kind-",
   377  			APIVersion: "ref2group/version",
   378  		},
   379  		Namespace: "ref2ns",
   380  	}
   381  	testcases := []struct {
   382  		file  string
   383  		nodes []*dotVertex
   384  		edges []dotEdge
   385  	}{
   386  		{
   387  			file: "empty.dot",
   388  		},
   389  		{
   390  			file: "simple.dot",
   391  			nodes: []*dotVertex{
   392  				NewDOTVertex(alphaNode()),
   393  				NewDOTVertex(bravoNode()),
   394  				NewDOTVertex(charlieNode()),
   395  				NewDOTVertex(deltaNode()),
   396  				NewDOTVertex(foxtrotNode()),
   397  				NewDOTVertex(golfNode()),
   398  			},
   399  			edges: []dotEdge{
   400  				{F: types.UID("alpha"), T: types.UID("bravo")},
   401  				{F: types.UID("alpha"), T: types.UID("charlie")},
   402  				{F: types.UID("delta"), T: types.UID("foxtrot")},
   403  				{F: types.UID("foxtrot"), T: types.UID("golf")},
   404  			},
   405  		},
   406  		{
   407  			file: "escaping.dot",
   408  			nodes: []*dotVertex{
   409  				NewDOTVertex(makeNode(ref1, withOwners(ref2))),
   410  				NewDOTVertex(makeNode(ref2)),
   411  			},
   412  			edges: []dotEdge{
   413  				{F: types.UID(ref1.UID), T: types.UID(ref2.UID)},
   414  			},
   415  		},
   416  	}
   417  
   418  	for _, tc := range testcases {
   419  		t.Run(tc.file, func(t *testing.T) {
   420  			goldenData, err := os.ReadFile(filepath.Join("testdata", tc.file))
   421  			if err != nil {
   422  				t.Fatal(err)
   423  			}
   424  			b := bytes.NewBuffer(nil)
   425  			if err := marshalDOT(b, tc.nodes, tc.edges); err != nil {
   426  				t.Fatal(err)
   427  			}
   428  
   429  			if e, a := string(goldenData), string(b.Bytes()); cmp.Diff(e, a) != "" {
   430  				t.Logf("got\n%s", string(a))
   431  				t.Fatalf("unexpected diff:\n%s", cmp.Diff(e, a))
   432  			}
   433  		})
   434  	}
   435  }
   436  

View as plain text