garbagecollector_test.go 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961
  1. /*
  2. Copyright 2016 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package garbagecollector
  14. import (
  15. "fmt"
  16. "net/http"
  17. "net/http/httptest"
  18. "reflect"
  19. "strings"
  20. "sync"
  21. "testing"
  22. "time"
  23. "github.com/stretchr/testify/assert"
  24. _ "k8s.io/kubernetes/pkg/apis/core/install"
  25. "k8s.io/api/core/v1"
  26. "k8s.io/apimachinery/pkg/api/meta"
  27. "k8s.io/apimachinery/pkg/api/meta/testrestmapper"
  28. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  29. "k8s.io/apimachinery/pkg/runtime/schema"
  30. "k8s.io/apimachinery/pkg/types"
  31. "k8s.io/apimachinery/pkg/util/json"
  32. "k8s.io/apimachinery/pkg/util/sets"
  33. "k8s.io/apimachinery/pkg/util/strategicpatch"
  34. "k8s.io/client-go/discovery"
  35. "k8s.io/client-go/dynamic"
  36. "k8s.io/client-go/dynamic/dynamicinformer"
  37. "k8s.io/client-go/informers"
  38. "k8s.io/client-go/kubernetes"
  39. "k8s.io/client-go/kubernetes/fake"
  40. restclient "k8s.io/client-go/rest"
  41. "k8s.io/client-go/util/workqueue"
  42. "k8s.io/kubernetes/pkg/api/legacyscheme"
  43. "k8s.io/kubernetes/pkg/controller"
  44. )
  45. type testRESTMapper struct {
  46. meta.RESTMapper
  47. }
  48. func (_ *testRESTMapper) Reset() {}
  49. func TestGarbageCollectorConstruction(t *testing.T) {
  50. config := &restclient.Config{}
  51. tweakableRM := meta.NewDefaultRESTMapper(nil)
  52. rm := &testRESTMapper{meta.MultiRESTMapper{tweakableRM, testrestmapper.TestOnlyStaticRESTMapper(legacyscheme.Scheme)}}
  53. dynamicClient, err := dynamic.NewForConfig(config)
  54. if err != nil {
  55. t.Fatal(err)
  56. }
  57. podResource := map[schema.GroupVersionResource]struct{}{
  58. {Version: "v1", Resource: "pods"}: {},
  59. }
  60. twoResources := map[schema.GroupVersionResource]struct{}{
  61. {Version: "v1", Resource: "pods"}: {},
  62. {Group: "tpr.io", Version: "v1", Resource: "unknown"}: {},
  63. }
  64. client := fake.NewSimpleClientset()
  65. sharedInformers := informers.NewSharedInformerFactory(client, 0)
  66. dynamicInformers := dynamicinformer.NewDynamicSharedInformerFactory(dynamicClient, 0)
  67. // No monitor will be constructed for the non-core resource, but the GC
  68. // construction will not fail.
  69. alwaysStarted := make(chan struct{})
  70. close(alwaysStarted)
  71. gc, err := NewGarbageCollector(dynamicClient, rm, twoResources, map[schema.GroupResource]struct{}{},
  72. controller.NewInformerFactory(sharedInformers, dynamicInformers), alwaysStarted)
  73. if err != nil {
  74. t.Fatal(err)
  75. }
  76. assert.Equal(t, 1, len(gc.dependencyGraphBuilder.monitors))
  77. // Make sure resource monitor syncing creates and stops resource monitors.
  78. tweakableRM.Add(schema.GroupVersionKind{Group: "tpr.io", Version: "v1", Kind: "unknown"}, nil)
  79. err = gc.resyncMonitors(twoResources)
  80. if err != nil {
  81. t.Errorf("Failed adding a monitor: %v", err)
  82. }
  83. assert.Equal(t, 2, len(gc.dependencyGraphBuilder.monitors))
  84. err = gc.resyncMonitors(podResource)
  85. if err != nil {
  86. t.Errorf("Failed removing a monitor: %v", err)
  87. }
  88. assert.Equal(t, 1, len(gc.dependencyGraphBuilder.monitors))
  89. // Make sure the syncing mechanism also works after Run() has been called
  90. stopCh := make(chan struct{})
  91. defer close(stopCh)
  92. go gc.Run(1, stopCh)
  93. err = gc.resyncMonitors(twoResources)
  94. if err != nil {
  95. t.Errorf("Failed adding a monitor: %v", err)
  96. }
  97. assert.Equal(t, 2, len(gc.dependencyGraphBuilder.monitors))
  98. err = gc.resyncMonitors(podResource)
  99. if err != nil {
  100. t.Errorf("Failed removing a monitor: %v", err)
  101. }
  102. assert.Equal(t, 1, len(gc.dependencyGraphBuilder.monitors))
  103. }
  104. // fakeAction records information about requests to aid in testing.
  105. type fakeAction struct {
  106. method string
  107. path string
  108. query string
  109. }
  110. // String returns method=path to aid in testing
  111. func (f *fakeAction) String() string {
  112. return strings.Join([]string{f.method, f.path}, "=")
  113. }
  114. type FakeResponse struct {
  115. statusCode int
  116. content []byte
  117. }
  118. // fakeActionHandler holds a list of fakeActions received
  119. type fakeActionHandler struct {
  120. // statusCode and content returned by this handler for different method + path.
  121. response map[string]FakeResponse
  122. lock sync.Mutex
  123. actions []fakeAction
  124. }
  125. // ServeHTTP logs the action that occurred and always returns the associated status code
  126. func (f *fakeActionHandler) ServeHTTP(response http.ResponseWriter, request *http.Request) {
  127. func() {
  128. f.lock.Lock()
  129. defer f.lock.Unlock()
  130. f.actions = append(f.actions, fakeAction{method: request.Method, path: request.URL.Path, query: request.URL.RawQuery})
  131. fakeResponse, ok := f.response[request.Method+request.URL.Path]
  132. if !ok {
  133. fakeResponse.statusCode = 200
  134. fakeResponse.content = []byte("{\"kind\": \"List\"}")
  135. }
  136. response.Header().Set("Content-Type", "application/json")
  137. response.WriteHeader(fakeResponse.statusCode)
  138. response.Write(fakeResponse.content)
  139. }()
  140. // This is to allow the fakeActionHandler to simulate a watch being opened
  141. if strings.Contains(request.URL.RawQuery, "watch=true") {
  142. hijacker, ok := response.(http.Hijacker)
  143. if !ok {
  144. return
  145. }
  146. connection, _, err := hijacker.Hijack()
  147. if err != nil {
  148. return
  149. }
  150. defer connection.Close()
  151. time.Sleep(30 * time.Second)
  152. }
  153. }
  154. // testServerAndClientConfig returns a server that listens and a config that can reference it
  155. func testServerAndClientConfig(handler func(http.ResponseWriter, *http.Request)) (*httptest.Server, *restclient.Config) {
  156. srv := httptest.NewServer(http.HandlerFunc(handler))
  157. config := &restclient.Config{
  158. Host: srv.URL,
  159. }
  160. return srv, config
  161. }
  162. type garbageCollector struct {
  163. *GarbageCollector
  164. stop chan struct{}
  165. }
  166. func setupGC(t *testing.T, config *restclient.Config) garbageCollector {
  167. dynamicClient, err := dynamic.NewForConfig(config)
  168. if err != nil {
  169. t.Fatal(err)
  170. }
  171. podResource := map[schema.GroupVersionResource]struct{}{{Version: "v1", Resource: "pods"}: {}}
  172. client := fake.NewSimpleClientset()
  173. sharedInformers := informers.NewSharedInformerFactory(client, 0)
  174. alwaysStarted := make(chan struct{})
  175. close(alwaysStarted)
  176. gc, err := NewGarbageCollector(dynamicClient, &testRESTMapper{testrestmapper.TestOnlyStaticRESTMapper(legacyscheme.Scheme)}, podResource, ignoredResources, sharedInformers, alwaysStarted)
  177. if err != nil {
  178. t.Fatal(err)
  179. }
  180. stop := make(chan struct{})
  181. go sharedInformers.Start(stop)
  182. return garbageCollector{gc, stop}
  183. }
  184. func getPod(podName string, ownerReferences []metav1.OwnerReference) *v1.Pod {
  185. return &v1.Pod{
  186. TypeMeta: metav1.TypeMeta{
  187. Kind: "Pod",
  188. APIVersion: "v1",
  189. },
  190. ObjectMeta: metav1.ObjectMeta{
  191. Name: podName,
  192. Namespace: "ns1",
  193. OwnerReferences: ownerReferences,
  194. },
  195. }
  196. }
  197. func serilizeOrDie(t *testing.T, object interface{}) []byte {
  198. data, err := json.Marshal(object)
  199. if err != nil {
  200. t.Fatal(err)
  201. }
  202. return data
  203. }
  204. // test the attemptToDeleteItem function making the expected actions.
  205. func TestAttemptToDeleteItem(t *testing.T) {
  206. pod := getPod("ToBeDeletedPod", []metav1.OwnerReference{
  207. {
  208. Kind: "ReplicationController",
  209. Name: "owner1",
  210. UID: "123",
  211. APIVersion: "v1",
  212. },
  213. })
  214. testHandler := &fakeActionHandler{
  215. response: map[string]FakeResponse{
  216. "GET" + "/api/v1/namespaces/ns1/replicationcontrollers/owner1": {
  217. 404,
  218. []byte{},
  219. },
  220. "GET" + "/api/v1/namespaces/ns1/pods/ToBeDeletedPod": {
  221. 200,
  222. serilizeOrDie(t, pod),
  223. },
  224. },
  225. }
  226. srv, clientConfig := testServerAndClientConfig(testHandler.ServeHTTP)
  227. defer srv.Close()
  228. gc := setupGC(t, clientConfig)
  229. defer close(gc.stop)
  230. item := &node{
  231. identity: objectReference{
  232. OwnerReference: metav1.OwnerReference{
  233. Kind: pod.Kind,
  234. APIVersion: pod.APIVersion,
  235. Name: pod.Name,
  236. UID: pod.UID,
  237. },
  238. Namespace: pod.Namespace,
  239. },
  240. // owners are intentionally left empty. The attemptToDeleteItem routine should get the latest item from the server.
  241. owners: nil,
  242. }
  243. err := gc.attemptToDeleteItem(item)
  244. if err != nil {
  245. t.Errorf("Unexpected Error: %v", err)
  246. }
  247. expectedActionSet := sets.NewString()
  248. expectedActionSet.Insert("GET=/api/v1/namespaces/ns1/replicationcontrollers/owner1")
  249. expectedActionSet.Insert("DELETE=/api/v1/namespaces/ns1/pods/ToBeDeletedPod")
  250. expectedActionSet.Insert("GET=/api/v1/namespaces/ns1/pods/ToBeDeletedPod")
  251. actualActionSet := sets.NewString()
  252. for _, action := range testHandler.actions {
  253. actualActionSet.Insert(action.String())
  254. }
  255. if !expectedActionSet.Equal(actualActionSet) {
  256. t.Errorf("expected actions:\n%v\n but got:\n%v\nDifference:\n%v", expectedActionSet,
  257. actualActionSet, expectedActionSet.Difference(actualActionSet))
  258. }
  259. }
  260. // verifyGraphInvariants verifies that all of a node's owners list the node as a
  261. // dependent and vice versa. uidToNode has all the nodes in the graph.
  262. func verifyGraphInvariants(scenario string, uidToNode map[types.UID]*node, t *testing.T) {
  263. for myUID, node := range uidToNode {
  264. for dependentNode := range node.dependents {
  265. found := false
  266. for _, owner := range dependentNode.owners {
  267. if owner.UID == myUID {
  268. found = true
  269. break
  270. }
  271. }
  272. if !found {
  273. t.Errorf("scenario: %s: node %s has node %s as a dependent, but it's not present in the latter node's owners list", scenario, node.identity, dependentNode.identity)
  274. }
  275. }
  276. for _, owner := range node.owners {
  277. ownerNode, ok := uidToNode[owner.UID]
  278. if !ok {
  279. // It's possible that the owner node doesn't exist
  280. continue
  281. }
  282. if _, ok := ownerNode.dependents[node]; !ok {
  283. t.Errorf("node %s has node %s as an owner, but it's not present in the latter node's dependents list", node.identity, ownerNode.identity)
  284. }
  285. }
  286. }
  287. }
  288. func createEvent(eventType eventType, selfUID string, owners []string) event {
  289. var ownerReferences []metav1.OwnerReference
  290. for i := 0; i < len(owners); i++ {
  291. ownerReferences = append(ownerReferences, metav1.OwnerReference{UID: types.UID(owners[i])})
  292. }
  293. return event{
  294. eventType: eventType,
  295. obj: &v1.Pod{
  296. ObjectMeta: metav1.ObjectMeta{
  297. UID: types.UID(selfUID),
  298. OwnerReferences: ownerReferences,
  299. },
  300. },
  301. }
  302. }
  303. func TestProcessEvent(t *testing.T) {
  304. var testScenarios = []struct {
  305. name string
  306. // a series of events that will be supplied to the
  307. // GraphBuilder.graphChanges.
  308. events []event
  309. }{
  310. {
  311. name: "test1",
  312. events: []event{
  313. createEvent(addEvent, "1", []string{}),
  314. createEvent(addEvent, "2", []string{"1"}),
  315. createEvent(addEvent, "3", []string{"1", "2"}),
  316. },
  317. },
  318. {
  319. name: "test2",
  320. events: []event{
  321. createEvent(addEvent, "1", []string{}),
  322. createEvent(addEvent, "2", []string{"1"}),
  323. createEvent(addEvent, "3", []string{"1", "2"}),
  324. createEvent(addEvent, "4", []string{"2"}),
  325. createEvent(deleteEvent, "2", []string{"doesn't matter"}),
  326. },
  327. },
  328. {
  329. name: "test3",
  330. events: []event{
  331. createEvent(addEvent, "1", []string{}),
  332. createEvent(addEvent, "2", []string{"1"}),
  333. createEvent(addEvent, "3", []string{"1", "2"}),
  334. createEvent(addEvent, "4", []string{"3"}),
  335. createEvent(updateEvent, "2", []string{"4"}),
  336. },
  337. },
  338. {
  339. name: "reverse test2",
  340. events: []event{
  341. createEvent(addEvent, "4", []string{"2"}),
  342. createEvent(addEvent, "3", []string{"1", "2"}),
  343. createEvent(addEvent, "2", []string{"1"}),
  344. createEvent(addEvent, "1", []string{}),
  345. createEvent(deleteEvent, "2", []string{"doesn't matter"}),
  346. },
  347. },
  348. }
  349. alwaysStarted := make(chan struct{})
  350. close(alwaysStarted)
  351. for _, scenario := range testScenarios {
  352. dependencyGraphBuilder := &GraphBuilder{
  353. informersStarted: alwaysStarted,
  354. graphChanges: workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()),
  355. uidToNode: &concurrentUIDToNode{
  356. uidToNodeLock: sync.RWMutex{},
  357. uidToNode: make(map[types.UID]*node),
  358. },
  359. attemptToDelete: workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()),
  360. absentOwnerCache: NewUIDCache(2),
  361. }
  362. for i := 0; i < len(scenario.events); i++ {
  363. dependencyGraphBuilder.graphChanges.Add(&scenario.events[i])
  364. dependencyGraphBuilder.processGraphChanges()
  365. verifyGraphInvariants(scenario.name, dependencyGraphBuilder.uidToNode.uidToNode, t)
  366. }
  367. }
  368. }
  369. // TestDependentsRace relies on golang's data race detector to check if there is
  370. // data race among in the dependents field.
  371. func TestDependentsRace(t *testing.T) {
  372. gc := setupGC(t, &restclient.Config{})
  373. defer close(gc.stop)
  374. const updates = 100
  375. owner := &node{dependents: make(map[*node]struct{})}
  376. ownerUID := types.UID("owner")
  377. gc.dependencyGraphBuilder.uidToNode.Write(owner)
  378. go func() {
  379. for i := 0; i < updates; i++ {
  380. dependent := &node{}
  381. gc.dependencyGraphBuilder.addDependentToOwners(dependent, []metav1.OwnerReference{{UID: ownerUID}})
  382. gc.dependencyGraphBuilder.removeDependentFromOwners(dependent, []metav1.OwnerReference{{UID: ownerUID}})
  383. }
  384. }()
  385. go func() {
  386. gc.attemptToOrphan.Add(owner)
  387. for i := 0; i < updates; i++ {
  388. gc.attemptToOrphanWorker()
  389. }
  390. }()
  391. }
  392. func podToGCNode(pod *v1.Pod) *node {
  393. return &node{
  394. identity: objectReference{
  395. OwnerReference: metav1.OwnerReference{
  396. Kind: pod.Kind,
  397. APIVersion: pod.APIVersion,
  398. Name: pod.Name,
  399. UID: pod.UID,
  400. },
  401. Namespace: pod.Namespace,
  402. },
  403. // owners are intentionally left empty. The attemptToDeleteItem routine should get the latest item from the server.
  404. owners: nil,
  405. }
  406. }
  407. func TestAbsentUIDCache(t *testing.T) {
  408. rc1Pod1 := getPod("rc1Pod1", []metav1.OwnerReference{
  409. {
  410. Kind: "ReplicationController",
  411. Name: "rc1",
  412. UID: "1",
  413. APIVersion: "v1",
  414. },
  415. })
  416. rc1Pod2 := getPod("rc1Pod2", []metav1.OwnerReference{
  417. {
  418. Kind: "ReplicationController",
  419. Name: "rc1",
  420. UID: "1",
  421. APIVersion: "v1",
  422. },
  423. })
  424. rc2Pod1 := getPod("rc2Pod1", []metav1.OwnerReference{
  425. {
  426. Kind: "ReplicationController",
  427. Name: "rc2",
  428. UID: "2",
  429. APIVersion: "v1",
  430. },
  431. })
  432. rc3Pod1 := getPod("rc3Pod1", []metav1.OwnerReference{
  433. {
  434. Kind: "ReplicationController",
  435. Name: "rc3",
  436. UID: "3",
  437. APIVersion: "v1",
  438. },
  439. })
  440. testHandler := &fakeActionHandler{
  441. response: map[string]FakeResponse{
  442. "GET" + "/api/v1/namespaces/ns1/pods/rc1Pod1": {
  443. 200,
  444. serilizeOrDie(t, rc1Pod1),
  445. },
  446. "GET" + "/api/v1/namespaces/ns1/pods/rc1Pod2": {
  447. 200,
  448. serilizeOrDie(t, rc1Pod2),
  449. },
  450. "GET" + "/api/v1/namespaces/ns1/pods/rc2Pod1": {
  451. 200,
  452. serilizeOrDie(t, rc2Pod1),
  453. },
  454. "GET" + "/api/v1/namespaces/ns1/pods/rc3Pod1": {
  455. 200,
  456. serilizeOrDie(t, rc3Pod1),
  457. },
  458. "GET" + "/api/v1/namespaces/ns1/replicationcontrollers/rc1": {
  459. 404,
  460. []byte{},
  461. },
  462. "GET" + "/api/v1/namespaces/ns1/replicationcontrollers/rc2": {
  463. 404,
  464. []byte{},
  465. },
  466. "GET" + "/api/v1/namespaces/ns1/replicationcontrollers/rc3": {
  467. 404,
  468. []byte{},
  469. },
  470. },
  471. }
  472. srv, clientConfig := testServerAndClientConfig(testHandler.ServeHTTP)
  473. defer srv.Close()
  474. gc := setupGC(t, clientConfig)
  475. defer close(gc.stop)
  476. gc.absentOwnerCache = NewUIDCache(2)
  477. gc.attemptToDeleteItem(podToGCNode(rc1Pod1))
  478. gc.attemptToDeleteItem(podToGCNode(rc2Pod1))
  479. // rc1 should already be in the cache, no request should be sent. rc1 should be promoted in the UIDCache
  480. gc.attemptToDeleteItem(podToGCNode(rc1Pod2))
  481. // after this call, rc2 should be evicted from the UIDCache
  482. gc.attemptToDeleteItem(podToGCNode(rc3Pod1))
  483. // check cache
  484. if !gc.absentOwnerCache.Has(types.UID("1")) {
  485. t.Errorf("expected rc1 to be in the cache")
  486. }
  487. if gc.absentOwnerCache.Has(types.UID("2")) {
  488. t.Errorf("expected rc2 to not exist in the cache")
  489. }
  490. if !gc.absentOwnerCache.Has(types.UID("3")) {
  491. t.Errorf("expected rc3 to be in the cache")
  492. }
  493. // check the request sent to the server
  494. count := 0
  495. for _, action := range testHandler.actions {
  496. if action.String() == "GET=/api/v1/namespaces/ns1/replicationcontrollers/rc1" {
  497. count++
  498. }
  499. }
  500. if count != 1 {
  501. t.Errorf("expected only 1 GET rc1 request, got %d", count)
  502. }
  503. }
  504. func TestDeleteOwnerRefPatch(t *testing.T) {
  505. original := v1.Pod{
  506. ObjectMeta: metav1.ObjectMeta{
  507. UID: "100",
  508. OwnerReferences: []metav1.OwnerReference{
  509. {UID: "1"},
  510. {UID: "2"},
  511. {UID: "3"},
  512. },
  513. },
  514. }
  515. originalData := serilizeOrDie(t, original)
  516. expected := v1.Pod{
  517. ObjectMeta: metav1.ObjectMeta{
  518. UID: "100",
  519. OwnerReferences: []metav1.OwnerReference{
  520. {UID: "1"},
  521. },
  522. },
  523. }
  524. patch := deleteOwnerRefStrategicMergePatch("100", "2", "3")
  525. patched, err := strategicpatch.StrategicMergePatch(originalData, patch, v1.Pod{})
  526. if err != nil {
  527. t.Fatal(err)
  528. }
  529. var got v1.Pod
  530. if err := json.Unmarshal(patched, &got); err != nil {
  531. t.Fatal(err)
  532. }
  533. if !reflect.DeepEqual(expected, got) {
  534. t.Errorf("expected: %#v,\ngot: %#v", expected, got)
  535. }
  536. }
  537. func TestUnblockOwnerReference(t *testing.T) {
  538. trueVar := true
  539. falseVar := false
  540. original := v1.Pod{
  541. ObjectMeta: metav1.ObjectMeta{
  542. UID: "100",
  543. OwnerReferences: []metav1.OwnerReference{
  544. {UID: "1", BlockOwnerDeletion: &trueVar},
  545. {UID: "2", BlockOwnerDeletion: &falseVar},
  546. {UID: "3"},
  547. },
  548. },
  549. }
  550. originalData := serilizeOrDie(t, original)
  551. expected := v1.Pod{
  552. ObjectMeta: metav1.ObjectMeta{
  553. UID: "100",
  554. OwnerReferences: []metav1.OwnerReference{
  555. {UID: "1", BlockOwnerDeletion: &falseVar},
  556. {UID: "2", BlockOwnerDeletion: &falseVar},
  557. {UID: "3"},
  558. },
  559. },
  560. }
  561. accessor, err := meta.Accessor(&original)
  562. if err != nil {
  563. t.Fatal(err)
  564. }
  565. n := node{
  566. owners: accessor.GetOwnerReferences(),
  567. }
  568. patch, err := n.unblockOwnerReferencesStrategicMergePatch()
  569. if err != nil {
  570. t.Fatal(err)
  571. }
  572. patched, err := strategicpatch.StrategicMergePatch(originalData, patch, v1.Pod{})
  573. if err != nil {
  574. t.Fatal(err)
  575. }
  576. var got v1.Pod
  577. if err := json.Unmarshal(patched, &got); err != nil {
  578. t.Fatal(err)
  579. }
  580. if !reflect.DeepEqual(expected, got) {
  581. t.Errorf("expected: %#v,\ngot: %#v", expected, got)
  582. t.Errorf("expected: %#v,\ngot: %#v", expected.OwnerReferences, got.OwnerReferences)
  583. for _, ref := range got.OwnerReferences {
  584. t.Errorf("ref.UID=%s, ref.BlockOwnerDeletion=%v", ref.UID, *ref.BlockOwnerDeletion)
  585. }
  586. }
  587. }
  588. func TestOrphanDependentsFailure(t *testing.T) {
  589. testHandler := &fakeActionHandler{
  590. response: map[string]FakeResponse{
  591. "PATCH" + "/api/v1/namespaces/ns1/pods/pod": {
  592. 409,
  593. []byte{},
  594. },
  595. },
  596. }
  597. srv, clientConfig := testServerAndClientConfig(testHandler.ServeHTTP)
  598. defer srv.Close()
  599. gc := setupGC(t, clientConfig)
  600. defer close(gc.stop)
  601. dependents := []*node{
  602. {
  603. identity: objectReference{
  604. OwnerReference: metav1.OwnerReference{
  605. Kind: "Pod",
  606. APIVersion: "v1",
  607. Name: "pod",
  608. },
  609. Namespace: "ns1",
  610. },
  611. },
  612. }
  613. err := gc.orphanDependents(objectReference{}, dependents)
  614. expected := `the server reported a conflict`
  615. if err == nil || !strings.Contains(err.Error(), expected) {
  616. if err != nil {
  617. t.Errorf("expected error contains text %q, got %q", expected, err.Error())
  618. } else {
  619. t.Errorf("expected error contains text %q, got nil", expected)
  620. }
  621. }
  622. }
  623. // TestGetDeletableResources ensures GetDeletableResources always returns
  624. // something usable regardless of discovery output.
  625. func TestGetDeletableResources(t *testing.T) {
  626. tests := map[string]struct {
  627. serverResources []*metav1.APIResourceList
  628. err error
  629. deletableResources map[schema.GroupVersionResource]struct{}
  630. }{
  631. "no error": {
  632. serverResources: []*metav1.APIResourceList{
  633. {
  634. // Valid GroupVersion
  635. GroupVersion: "apps/v1",
  636. APIResources: []metav1.APIResource{
  637. {Name: "pods", Namespaced: true, Kind: "Pod", Verbs: metav1.Verbs{"delete", "list", "watch"}},
  638. {Name: "services", Namespaced: true, Kind: "Service"},
  639. },
  640. },
  641. {
  642. // Invalid GroupVersion, should be ignored
  643. GroupVersion: "foo//whatever",
  644. APIResources: []metav1.APIResource{
  645. {Name: "bars", Namespaced: true, Kind: "Bar", Verbs: metav1.Verbs{"delete", "list", "watch"}},
  646. },
  647. },
  648. {
  649. // Valid GroupVersion, missing required verbs, should be ignored
  650. GroupVersion: "acme/v1",
  651. APIResources: []metav1.APIResource{
  652. {Name: "widgets", Namespaced: true, Kind: "Widget", Verbs: metav1.Verbs{"delete"}},
  653. },
  654. },
  655. },
  656. err: nil,
  657. deletableResources: map[schema.GroupVersionResource]struct{}{
  658. {Group: "apps", Version: "v1", Resource: "pods"}: {},
  659. },
  660. },
  661. "nonspecific failure, includes usable results": {
  662. serverResources: []*metav1.APIResourceList{
  663. {
  664. GroupVersion: "apps/v1",
  665. APIResources: []metav1.APIResource{
  666. {Name: "pods", Namespaced: true, Kind: "Pod", Verbs: metav1.Verbs{"delete", "list", "watch"}},
  667. {Name: "services", Namespaced: true, Kind: "Service"},
  668. },
  669. },
  670. },
  671. err: fmt.Errorf("internal error"),
  672. deletableResources: map[schema.GroupVersionResource]struct{}{
  673. {Group: "apps", Version: "v1", Resource: "pods"}: {},
  674. },
  675. },
  676. "partial discovery failure, includes usable results": {
  677. serverResources: []*metav1.APIResourceList{
  678. {
  679. GroupVersion: "apps/v1",
  680. APIResources: []metav1.APIResource{
  681. {Name: "pods", Namespaced: true, Kind: "Pod", Verbs: metav1.Verbs{"delete", "list", "watch"}},
  682. {Name: "services", Namespaced: true, Kind: "Service"},
  683. },
  684. },
  685. },
  686. err: &discovery.ErrGroupDiscoveryFailed{
  687. Groups: map[schema.GroupVersion]error{
  688. {Group: "foo", Version: "v1"}: fmt.Errorf("discovery failure"),
  689. },
  690. },
  691. deletableResources: map[schema.GroupVersionResource]struct{}{
  692. {Group: "apps", Version: "v1", Resource: "pods"}: {},
  693. },
  694. },
  695. "discovery failure, no results": {
  696. serverResources: nil,
  697. err: fmt.Errorf("internal error"),
  698. deletableResources: map[schema.GroupVersionResource]struct{}{},
  699. },
  700. }
  701. for name, test := range tests {
  702. t.Logf("testing %q", name)
  703. client := &fakeServerResources{
  704. PreferredResources: test.serverResources,
  705. Error: test.err,
  706. }
  707. actual := GetDeletableResources(client)
  708. if !reflect.DeepEqual(test.deletableResources, actual) {
  709. t.Errorf("expected resources:\n%v\ngot:\n%v", test.deletableResources, actual)
  710. }
  711. }
  712. }
  713. // TestGarbageCollectorSync ensures that a discovery client error
  714. // will not cause the garbage collector to block infinitely.
  715. func TestGarbageCollectorSync(t *testing.T) {
  716. serverResources := []*metav1.APIResourceList{
  717. {
  718. GroupVersion: "v1",
  719. APIResources: []metav1.APIResource{
  720. {Name: "pods", Namespaced: true, Kind: "Pod", Verbs: metav1.Verbs{"delete", "list", "watch"}},
  721. },
  722. },
  723. }
  724. unsyncableServerResources := []*metav1.APIResourceList{
  725. {
  726. GroupVersion: "v1",
  727. APIResources: []metav1.APIResource{
  728. {Name: "pods", Namespaced: true, Kind: "Pod", Verbs: metav1.Verbs{"delete", "list", "watch"}},
  729. {Name: "secrets", Namespaced: true, Kind: "Secret", Verbs: metav1.Verbs{"delete", "list", "watch"}},
  730. },
  731. },
  732. }
  733. fakeDiscoveryClient := &fakeServerResources{
  734. PreferredResources: serverResources,
  735. Error: nil,
  736. Lock: sync.Mutex{},
  737. InterfaceUsedCount: 0,
  738. }
  739. testHandler := &fakeActionHandler{
  740. response: map[string]FakeResponse{
  741. "GET" + "/api/v1/pods": {
  742. 200,
  743. []byte("{}"),
  744. },
  745. "GET" + "/api/v1/secrets": {
  746. 404,
  747. []byte("{}"),
  748. },
  749. },
  750. }
  751. srv, clientConfig := testServerAndClientConfig(testHandler.ServeHTTP)
  752. defer srv.Close()
  753. clientConfig.ContentConfig.NegotiatedSerializer = nil
  754. client, err := kubernetes.NewForConfig(clientConfig)
  755. if err != nil {
  756. t.Fatal(err)
  757. }
  758. rm := &testRESTMapper{testrestmapper.TestOnlyStaticRESTMapper(legacyscheme.Scheme)}
  759. dynamicClient, err := dynamic.NewForConfig(clientConfig)
  760. if err != nil {
  761. t.Fatal(err)
  762. }
  763. podResource := map[schema.GroupVersionResource]struct{}{
  764. {Group: "", Version: "v1", Resource: "pods"}: {},
  765. }
  766. sharedInformers := informers.NewSharedInformerFactory(client, 0)
  767. alwaysStarted := make(chan struct{})
  768. close(alwaysStarted)
  769. gc, err := NewGarbageCollector(dynamicClient, rm, podResource, map[schema.GroupResource]struct{}{}, sharedInformers, alwaysStarted)
  770. if err != nil {
  771. t.Fatal(err)
  772. }
  773. stopCh := make(chan struct{})
  774. defer close(stopCh)
  775. go gc.Run(1, stopCh)
  776. // The pseudo-code of GarbageCollector.Sync():
  777. // GarbageCollector.Sync(client, period, stopCh):
  778. // wait.Until() loops with `period` until the `stopCh` is closed :
  779. // wait.PollImmediateUntil() loops with 100ms (hardcode) util the `stopCh` is closed:
  780. // GetDeletableResources()
  781. // gc.resyncMonitors()
  782. // controller.WaitForCacheSync() loops with `syncedPollPeriod` (hardcoded to 100ms), until either its stop channel is closed after `period`, or all caches synced.
  783. //
  784. // Setting the period to 200ms allows the WaitForCacheSync() to check
  785. // for cache sync ~2 times in every wait.PollImmediateUntil() loop.
  786. //
  787. // The 1s sleep in the test allows GetDelableResources and
  788. // gc.resyncMoitors to run ~5 times to ensure the changes to the
  789. // fakeDiscoveryClient are picked up.
  790. go gc.Sync(fakeDiscoveryClient, 200*time.Millisecond, stopCh)
  791. // Wait until the sync discovers the initial resources
  792. time.Sleep(1 * time.Second)
  793. err = expectSyncNotBlocked(fakeDiscoveryClient, &gc.workerLock)
  794. if err != nil {
  795. t.Fatalf("Expected garbagecollector.Sync to be running but it is blocked: %v", err)
  796. }
  797. // Simulate the discovery client returning an error
  798. fakeDiscoveryClient.setPreferredResources(nil)
  799. fakeDiscoveryClient.setError(fmt.Errorf("Error calling discoveryClient.ServerPreferredResources()"))
  800. // Wait until sync discovers the change
  801. time.Sleep(1 * time.Second)
  802. // Remove the error from being returned and see if the garbage collector sync is still working
  803. fakeDiscoveryClient.setPreferredResources(serverResources)
  804. fakeDiscoveryClient.setError(nil)
  805. err = expectSyncNotBlocked(fakeDiscoveryClient, &gc.workerLock)
  806. if err != nil {
  807. t.Fatalf("Expected garbagecollector.Sync to still be running but it is blocked: %v", err)
  808. }
  809. // Simulate the discovery client returning a resource the restmapper can resolve, but will not sync caches
  810. fakeDiscoveryClient.setPreferredResources(unsyncableServerResources)
  811. fakeDiscoveryClient.setError(nil)
  812. // Wait until sync discovers the change
  813. time.Sleep(1 * time.Second)
  814. // Put the resources back to normal and ensure garbage collector sync recovers
  815. fakeDiscoveryClient.setPreferredResources(serverResources)
  816. fakeDiscoveryClient.setError(nil)
  817. err = expectSyncNotBlocked(fakeDiscoveryClient, &gc.workerLock)
  818. if err != nil {
  819. t.Fatalf("Expected garbagecollector.Sync to still be running but it is blocked: %v", err)
  820. }
  821. }
  822. func expectSyncNotBlocked(fakeDiscoveryClient *fakeServerResources, workerLock *sync.RWMutex) error {
  823. before := fakeDiscoveryClient.getInterfaceUsedCount()
  824. t := 1 * time.Second
  825. time.Sleep(t)
  826. after := fakeDiscoveryClient.getInterfaceUsedCount()
  827. if before == after {
  828. return fmt.Errorf("discoveryClient.ServerPreferredResources() called %d times over %v", after-before, t)
  829. }
  830. workerLockAcquired := make(chan struct{})
  831. go func() {
  832. workerLock.Lock()
  833. workerLock.Unlock()
  834. close(workerLockAcquired)
  835. }()
  836. select {
  837. case <-workerLockAcquired:
  838. return nil
  839. case <-time.After(t):
  840. return fmt.Errorf("workerLock blocked for at least %v", t)
  841. }
  842. }
  843. type fakeServerResources struct {
  844. PreferredResources []*metav1.APIResourceList
  845. Error error
  846. Lock sync.Mutex
  847. InterfaceUsedCount int
  848. }
  849. func (_ *fakeServerResources) ServerResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error) {
  850. return nil, nil
  851. }
  852. // Deprecated: use ServerGroupsAndResources instead.
  853. func (_ *fakeServerResources) ServerResources() ([]*metav1.APIResourceList, error) {
  854. return nil, nil
  855. }
  856. func (_ *fakeServerResources) ServerGroupsAndResources() ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
  857. return nil, nil, nil
  858. }
  859. func (f *fakeServerResources) ServerPreferredResources() ([]*metav1.APIResourceList, error) {
  860. f.Lock.Lock()
  861. defer f.Lock.Unlock()
  862. f.InterfaceUsedCount++
  863. return f.PreferredResources, f.Error
  864. }
  865. func (f *fakeServerResources) setPreferredResources(resources []*metav1.APIResourceList) {
  866. f.Lock.Lock()
  867. defer f.Lock.Unlock()
  868. f.PreferredResources = resources
  869. }
  870. func (f *fakeServerResources) setError(err error) {
  871. f.Lock.Lock()
  872. defer f.Lock.Unlock()
  873. f.Error = err
  874. }
  875. func (f *fakeServerResources) getInterfaceUsedCount() int {
  876. f.Lock.Lock()
  877. defer f.Lock.Unlock()
  878. return f.InterfaceUsedCount
  879. }
  880. func (_ *fakeServerResources) ServerPreferredNamespacedResources() ([]*metav1.APIResourceList, error) {
  881. return nil, nil
  882. }