2016-05-04 05:31:26 +00:00
/ *
2016-06-03 00:25:58 +00:00
Copyright 2016 The Kubernetes Authors .
2016-05-04 05:31:26 +00:00
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
package garbagecollector
import (
2017-11-07 18:19:43 +00:00
"fmt"
2016-05-04 05:31:26 +00:00
"net/http"
"net/http/httptest"
2017-02-23 19:16:13 +00:00
"reflect"
2016-05-04 05:31:26 +00:00
"strings"
"sync"
"testing"
2016-09-13 03:28:49 +00:00
"github.com/stretchr/testify/assert"
2017-11-08 22:34:54 +00:00
_ "k8s.io/kubernetes/pkg/apis/core/install"
2016-05-04 05:31:26 +00:00
2017-06-22 18:24:23 +00:00
"k8s.io/api/core/v1"
2017-02-23 19:16:13 +00:00
"k8s.io/apimachinery/pkg/api/meta"
2017-01-11 14:09:48 +00:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/json"
"k8s.io/apimachinery/pkg/util/sets"
2017-02-23 19:16:13 +00:00
"k8s.io/apimachinery/pkg/util/strategicpatch"
2017-11-07 18:19:43 +00:00
"k8s.io/client-go/discovery"
2017-01-25 19:00:30 +00:00
"k8s.io/client-go/dynamic"
2017-06-23 20:56:37 +00:00
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes/fake"
2017-01-19 18:27:59 +00:00
restclient "k8s.io/client-go/rest"
2017-01-27 15:20:40 +00:00
"k8s.io/client-go/util/workqueue"
2017-10-16 11:41:50 +00:00
"k8s.io/kubernetes/pkg/api/legacyscheme"
2016-09-13 03:28:49 +00:00
"k8s.io/kubernetes/pkg/controller/garbagecollector/metaonly"
2016-05-04 05:31:26 +00:00
)
2017-08-08 19:57:55 +00:00
type testRESTMapper struct {
meta . RESTMapper
}
func ( _ * testRESTMapper ) Reset ( ) { }
2017-05-17 22:54:58 +00:00
func TestGarbageCollectorConstruction ( t * testing . T ) {
2016-07-02 06:46:00 +00:00
config := & restclient . Config { }
config . ContentConfig . NegotiatedSerializer = serializer . DirectCodecFactory { CodecFactory : metaonly . NewMetadataCodecFactory ( ) }
2017-05-17 22:54:58 +00:00
tweakableRM := meta . NewDefaultRESTMapper ( nil , nil )
2017-10-16 11:41:50 +00:00
rm := & testRESTMapper { meta . MultiRESTMapper { tweakableRM , legacyscheme . Registry . RESTMapper ( ) } }
2017-05-17 22:54:58 +00:00
metaOnlyClientPool := dynamic . NewClientPool ( config , rm , dynamic . LegacyAPIPathResolverFunc )
2016-07-02 06:46:00 +00:00
config . ContentConfig . NegotiatedSerializer = nil
2017-05-17 22:54:58 +00:00
clientPool := dynamic . NewClientPool ( config , rm , dynamic . LegacyAPIPathResolverFunc )
2017-03-16 18:41:45 +00:00
podResource := map [ schema . GroupVersionResource ] struct { } {
2017-03-29 00:12:04 +00:00
{ Version : "v1" , Resource : "pods" } : { } ,
2017-05-17 22:54:58 +00:00
}
twoResources := map [ schema . GroupVersionResource ] struct { } {
{ Version : "v1" , Resource : "pods" } : { } ,
2017-03-29 00:12:04 +00:00
{ Group : "tpr.io" , Version : "v1" , Resource : "unknown" } : { } ,
2017-03-16 18:41:45 +00:00
}
2017-05-04 17:55:24 +00:00
client := fake . NewSimpleClientset ( )
sharedInformers := informers . NewSharedInformerFactory ( client , 0 )
2017-05-17 22:54:58 +00:00
// No monitor will be constructed for the non-core resource, but the GC
// construction will not fail.
2017-08-24 16:39:55 +00:00
alwaysStarted := make ( chan struct { } )
close ( alwaysStarted )
gc , err := NewGarbageCollector ( metaOnlyClientPool , clientPool , rm , twoResources , map [ schema . GroupResource ] struct { } { } , sharedInformers , alwaysStarted )
2016-05-04 05:31:26 +00:00
if err != nil {
t . Fatal ( err )
}
2017-02-23 19:16:13 +00:00
assert . Equal ( t , 1 , len ( gc . dependencyGraphBuilder . monitors ) )
2017-05-17 22:54:58 +00:00
// Make sure resource monitor syncing creates and stops resource monitors.
tweakableRM . Add ( schema . GroupVersionKind { Group : "tpr.io" , Version : "v1" , Kind : "unknown" } , nil )
err = gc . resyncMonitors ( twoResources )
if err != nil {
t . Errorf ( "Failed adding a monitor: %v" , err )
}
assert . Equal ( t , 2 , len ( gc . dependencyGraphBuilder . monitors ) )
err = gc . resyncMonitors ( podResource )
if err != nil {
t . Errorf ( "Failed removing a monitor: %v" , err )
}
assert . Equal ( t , 1 , len ( gc . dependencyGraphBuilder . monitors ) )
// Make sure the syncing mechanism also works after Run() has been called
stopCh := make ( chan struct { } )
defer close ( stopCh )
go gc . Run ( 1 , stopCh )
err = gc . resyncMonitors ( twoResources )
if err != nil {
t . Errorf ( "Failed adding a monitor: %v" , err )
}
assert . Equal ( t , 2 , len ( gc . dependencyGraphBuilder . monitors ) )
err = gc . resyncMonitors ( podResource )
if err != nil {
t . Errorf ( "Failed removing a monitor: %v" , err )
}
assert . Equal ( t , 1 , len ( gc . dependencyGraphBuilder . monitors ) )
2016-05-04 05:31:26 +00:00
}
// fakeAction records information about requests to aid in testing.
type fakeAction struct {
method string
path string
2016-05-28 22:10:25 +00:00
query string
2016-05-04 05:31:26 +00:00
}
// String returns method=path to aid in testing
func ( f * fakeAction ) String ( ) string {
return strings . Join ( [ ] string { f . method , f . path } , "=" )
}
type FakeResponse struct {
statusCode int
content [ ] byte
}
// fakeActionHandler holds a list of fakeActions received
type fakeActionHandler struct {
// statusCode and content returned by this handler for different method + path.
response map [ string ] FakeResponse
lock sync . Mutex
actions [ ] fakeAction
}
// ServeHTTP logs the action that occurred and always returns the associated status code
func ( f * fakeActionHandler ) ServeHTTP ( response http . ResponseWriter , request * http . Request ) {
f . lock . Lock ( )
defer f . lock . Unlock ( )
2016-05-28 22:10:25 +00:00
f . actions = append ( f . actions , fakeAction { method : request . Method , path : request . URL . Path , query : request . URL . RawQuery } )
2016-05-04 05:31:26 +00:00
fakeResponse , ok := f . response [ request . Method + request . URL . Path ]
if ! ok {
fakeResponse . statusCode = 200
fakeResponse . content = [ ] byte ( "{\"kind\": \"List\"}" )
}
2016-10-12 20:55:28 +00:00
response . Header ( ) . Set ( "Content-Type" , "application/json" )
2016-05-04 05:31:26 +00:00
response . WriteHeader ( fakeResponse . statusCode )
response . Write ( fakeResponse . content )
}
// testServerAndClientConfig returns a server that listens and a config that can reference it
func testServerAndClientConfig ( handler func ( http . ResponseWriter , * http . Request ) ) ( * httptest . Server , * restclient . Config ) {
srv := httptest . NewServer ( http . HandlerFunc ( handler ) )
config := & restclient . Config {
Host : srv . URL ,
}
return srv , config
}
2017-05-04 17:55:24 +00:00
type garbageCollector struct {
* GarbageCollector
stop chan struct { }
}
func setupGC ( t * testing . T , config * restclient . Config ) garbageCollector {
2016-08-22 21:36:23 +00:00
config . ContentConfig . NegotiatedSerializer = serializer . DirectCodecFactory { CodecFactory : metaonly . NewMetadataCodecFactory ( ) }
2017-10-16 11:41:50 +00:00
metaOnlyClientPool := dynamic . NewClientPool ( config , legacyscheme . Registry . RESTMapper ( ) , dynamic . LegacyAPIPathResolverFunc )
2016-08-22 21:36:23 +00:00
config . ContentConfig . NegotiatedSerializer = nil
2017-10-16 11:41:50 +00:00
clientPool := dynamic . NewClientPool ( config , legacyscheme . Registry . RESTMapper ( ) , dynamic . LegacyAPIPathResolverFunc )
2017-03-29 00:12:04 +00:00
podResource := map [ schema . GroupVersionResource ] struct { } { { Version : "v1" , Resource : "pods" } : { } }
2017-05-04 17:55:24 +00:00
client := fake . NewSimpleClientset ( )
sharedInformers := informers . NewSharedInformerFactory ( client , 0 )
2017-08-24 16:39:55 +00:00
alwaysStarted := make ( chan struct { } )
close ( alwaysStarted )
2017-10-16 11:41:50 +00:00
gc , err := NewGarbageCollector ( metaOnlyClientPool , clientPool , & testRESTMapper { legacyscheme . Registry . RESTMapper ( ) } , podResource , ignoredResources , sharedInformers , alwaysStarted )
2016-08-22 21:36:23 +00:00
if err != nil {
t . Fatal ( err )
}
2017-05-04 17:55:24 +00:00
stop := make ( chan struct { } )
go sharedInformers . Start ( stop )
return garbageCollector { gc , stop }
2016-08-22 21:36:23 +00:00
}
2016-12-09 18:16:33 +00:00
func getPod ( podName string , ownerReferences [ ] metav1 . OwnerReference ) * v1 . Pod {
2016-05-04 05:31:26 +00:00
return & v1 . Pod {
2016-12-03 18:57:26 +00:00
TypeMeta : metav1 . TypeMeta {
2016-05-04 05:31:26 +00:00
Kind : "Pod" ,
APIVersion : "v1" ,
} ,
2017-01-17 03:38:19 +00:00
ObjectMeta : metav1 . ObjectMeta {
2016-08-22 21:36:23 +00:00
Name : podName ,
Namespace : "ns1" ,
OwnerReferences : ownerReferences ,
2016-05-04 05:31:26 +00:00
} ,
}
}
2016-08-22 21:36:23 +00:00
func serilizeOrDie ( t * testing . T , object interface { } ) [ ] byte {
data , err := json . Marshal ( object )
2016-05-04 05:31:26 +00:00
if err != nil {
t . Fatal ( err )
}
2016-08-22 21:36:23 +00:00
return data
}
2017-02-23 19:16:13 +00:00
// test the attemptToDeleteItem function making the expected actions.
func TestAttemptToDeleteItem ( t * testing . T ) {
2016-12-09 18:16:33 +00:00
pod := getPod ( "ToBeDeletedPod" , [ ] metav1 . OwnerReference {
2016-08-22 21:36:23 +00:00
{
Kind : "ReplicationController" ,
Name : "owner1" ,
UID : "123" ,
APIVersion : "v1" ,
} ,
} )
2016-05-04 05:31:26 +00:00
testHandler := & fakeActionHandler {
response : map [ string ] FakeResponse {
"GET" + "/api/v1/namespaces/ns1/replicationcontrollers/owner1" : {
404 ,
[ ] byte { } ,
} ,
"GET" + "/api/v1/namespaces/ns1/pods/ToBeDeletedPod" : {
200 ,
2016-08-22 21:36:23 +00:00
serilizeOrDie ( t , pod ) ,
2016-05-04 05:31:26 +00:00
} ,
} ,
}
srv , clientConfig := testServerAndClientConfig ( testHandler . ServeHTTP )
defer srv . Close ( )
2017-05-04 17:55:24 +00:00
2016-08-22 21:36:23 +00:00
gc := setupGC ( t , clientConfig )
2017-05-04 17:55:24 +00:00
defer close ( gc . stop )
2016-05-04 05:31:26 +00:00
item := & node {
identity : objectReference {
2016-12-04 03:42:29 +00:00
OwnerReference : metav1 . OwnerReference {
2016-05-04 05:31:26 +00:00
Kind : pod . Kind ,
APIVersion : pod . APIVersion ,
Name : pod . Name ,
UID : pod . UID ,
} ,
Namespace : pod . Namespace ,
} ,
2017-02-23 19:16:13 +00:00
// owners are intentionally left empty. The attemptToDeleteItem routine should get the latest item from the server.
2016-05-04 05:31:26 +00:00
owners : nil ,
}
2017-02-23 19:16:13 +00:00
err := gc . attemptToDeleteItem ( item )
2016-05-04 05:31:26 +00:00
if err != nil {
t . Errorf ( "Unexpected Error: %v" , err )
}
expectedActionSet := sets . NewString ( )
expectedActionSet . Insert ( "GET=/api/v1/namespaces/ns1/replicationcontrollers/owner1" )
expectedActionSet . Insert ( "DELETE=/api/v1/namespaces/ns1/pods/ToBeDeletedPod" )
expectedActionSet . Insert ( "GET=/api/v1/namespaces/ns1/pods/ToBeDeletedPod" )
actualActionSet := sets . NewString ( )
for _ , action := range testHandler . actions {
actualActionSet . Insert ( action . String ( ) )
}
if ! expectedActionSet . Equal ( actualActionSet ) {
t . Errorf ( "expected actions:\n%v\n but got:\n%v\nDifference:\n%v" , expectedActionSet ,
actualActionSet , expectedActionSet . Difference ( actualActionSet ) )
}
}
// verifyGraphInvariants verifies that all of a node's owners list the node as a
// dependent and vice versa. uidToNode has all the nodes in the graph.
func verifyGraphInvariants ( scenario string , uidToNode map [ types . UID ] * node , t * testing . T ) {
for myUID , node := range uidToNode {
for dependentNode := range node . dependents {
found := false
for _ , owner := range dependentNode . owners {
if owner . UID == myUID {
found = true
break
}
}
if ! found {
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 )
}
}
for _ , owner := range node . owners {
ownerNode , ok := uidToNode [ owner . UID ]
if ! ok {
// It's possible that the owner node doesn't exist
continue
}
if _ , ok := ownerNode . dependents [ node ] ; ! ok {
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 )
}
}
}
}
func createEvent ( eventType eventType , selfUID string , owners [ ] string ) event {
2016-12-09 18:16:33 +00:00
var ownerReferences [ ] metav1 . OwnerReference
2016-05-04 05:31:26 +00:00
for i := 0 ; i < len ( owners ) ; i ++ {
2016-12-09 18:16:33 +00:00
ownerReferences = append ( ownerReferences , metav1 . OwnerReference { UID : types . UID ( owners [ i ] ) } )
2016-05-04 05:31:26 +00:00
}
return event {
eventType : eventType ,
2016-11-18 20:50:17 +00:00
obj : & v1 . Pod {
2017-01-17 03:38:19 +00:00
ObjectMeta : metav1 . ObjectMeta {
2016-05-04 05:31:26 +00:00
UID : types . UID ( selfUID ) ,
OwnerReferences : ownerReferences ,
} ,
} ,
}
}
func TestProcessEvent ( t * testing . T ) {
var testScenarios = [ ] struct {
name string
// a series of events that will be supplied to the
2017-06-29 13:42:47 +00:00
// GraphBuilder.graphChanges.
2016-05-04 05:31:26 +00:00
events [ ] event
} {
{
name : "test1" ,
events : [ ] event {
createEvent ( addEvent , "1" , [ ] string { } ) ,
createEvent ( addEvent , "2" , [ ] string { "1" } ) ,
createEvent ( addEvent , "3" , [ ] string { "1" , "2" } ) ,
} ,
} ,
{
name : "test2" ,
events : [ ] event {
createEvent ( addEvent , "1" , [ ] string { } ) ,
createEvent ( addEvent , "2" , [ ] string { "1" } ) ,
createEvent ( addEvent , "3" , [ ] string { "1" , "2" } ) ,
createEvent ( addEvent , "4" , [ ] string { "2" } ) ,
createEvent ( deleteEvent , "2" , [ ] string { "doesn't matter" } ) ,
} ,
} ,
{
name : "test3" ,
events : [ ] event {
createEvent ( addEvent , "1" , [ ] string { } ) ,
createEvent ( addEvent , "2" , [ ] string { "1" } ) ,
createEvent ( addEvent , "3" , [ ] string { "1" , "2" } ) ,
createEvent ( addEvent , "4" , [ ] string { "3" } ) ,
createEvent ( updateEvent , "2" , [ ] string { "4" } ) ,
} ,
} ,
{
name : "reverse test2" ,
events : [ ] event {
createEvent ( addEvent , "4" , [ ] string { "2" } ) ,
createEvent ( addEvent , "3" , [ ] string { "1" , "2" } ) ,
createEvent ( addEvent , "2" , [ ] string { "1" } ) ,
createEvent ( addEvent , "1" , [ ] string { } ) ,
createEvent ( deleteEvent , "2" , [ ] string { "doesn't matter" } ) ,
} ,
} ,
}
2017-08-24 16:39:55 +00:00
alwaysStarted := make ( chan struct { } )
close ( alwaysStarted )
2016-05-04 05:31:26 +00:00
for _ , scenario := range testScenarios {
2017-02-23 19:16:13 +00:00
dependencyGraphBuilder := & GraphBuilder {
2017-08-24 16:39:55 +00:00
informersStarted : alwaysStarted ,
graphChanges : workqueue . NewRateLimitingQueue ( workqueue . DefaultControllerRateLimiter ( ) ) ,
2016-05-18 03:24:42 +00:00
uidToNode : & concurrentUIDToNode {
2017-02-23 19:16:13 +00:00
uidToNodeLock : sync . RWMutex { } ,
uidToNode : make ( map [ types . UID ] * node ) ,
2016-05-04 05:31:26 +00:00
} ,
2017-02-23 19:16:13 +00:00
attemptToDelete : workqueue . NewRateLimitingQueue ( workqueue . DefaultControllerRateLimiter ( ) ) ,
absentOwnerCache : NewUIDCache ( 2 ) ,
2016-05-04 05:31:26 +00:00
}
for i := 0 ; i < len ( scenario . events ) ; i ++ {
2017-02-23 19:16:13 +00:00
dependencyGraphBuilder . graphChanges . Add ( & scenario . events [ i ] )
dependencyGraphBuilder . processGraphChanges ( )
verifyGraphInvariants ( scenario . name , dependencyGraphBuilder . uidToNode . uidToNode , t )
2016-05-04 05:31:26 +00:00
}
}
}
2016-05-18 03:24:42 +00:00
// TestDependentsRace relies on golang's data race detector to check if there is
// data race among in the dependents field.
func TestDependentsRace ( t * testing . T ) {
2016-08-22 21:36:23 +00:00
gc := setupGC ( t , & restclient . Config { } )
2017-05-04 17:55:24 +00:00
defer close ( gc . stop )
2016-05-18 03:24:42 +00:00
const updates = 100
2016-08-02 06:13:17 +00:00
owner := & node { dependents : make ( map [ * node ] struct { } ) }
2016-05-18 03:24:42 +00:00
ownerUID := types . UID ( "owner" )
2017-02-23 19:16:13 +00:00
gc . dependencyGraphBuilder . uidToNode . Write ( owner )
2016-05-18 03:24:42 +00:00
go func ( ) {
for i := 0 ; i < updates ; i ++ {
dependent := & node { }
2017-02-23 19:16:13 +00:00
gc . dependencyGraphBuilder . addDependentToOwners ( dependent , [ ] metav1 . OwnerReference { { UID : ownerUID } } )
gc . dependencyGraphBuilder . removeDependentFromOwners ( dependent , [ ] metav1 . OwnerReference { { UID : ownerUID } } )
2016-05-18 03:24:42 +00:00
}
} ( )
go func ( ) {
2017-02-23 19:16:13 +00:00
gc . attemptToOrphan . Add ( owner )
2016-05-18 03:24:42 +00:00
for i := 0 ; i < updates ; i ++ {
2017-02-23 19:16:13 +00:00
gc . attemptToOrphanWorker ( )
2016-05-18 03:24:42 +00:00
}
} ( )
}
2016-05-28 22:10:25 +00:00
// test the list and watch functions correctly converts the ListOptions
func TestGCListWatcher ( t * testing . T ) {
testHandler := & fakeActionHandler { }
srv , clientConfig := testServerAndClientConfig ( testHandler . ServeHTTP )
defer srv . Close ( )
2017-10-16 11:41:50 +00:00
clientPool := dynamic . NewClientPool ( clientConfig , legacyscheme . Registry . RESTMapper ( ) , dynamic . LegacyAPIPathResolverFunc )
2016-11-21 02:55:31 +00:00
podResource := schema . GroupVersionResource { Version : "v1" , Resource : "pods" }
2016-09-13 03:28:49 +00:00
client , err := clientPool . ClientForGroupVersionResource ( podResource )
2016-05-28 22:10:25 +00:00
if err != nil {
t . Fatal ( err )
}
2017-02-23 19:16:13 +00:00
lw := listWatcher ( client , podResource )
2017-09-03 19:04:54 +00:00
lw . DisableChunking = true
2017-02-23 19:16:13 +00:00
if _ , err := lw . Watch ( metav1 . ListOptions { ResourceVersion : "1" } ) ; err != nil {
t . Fatal ( err )
}
if _ , err := lw . List ( metav1 . ListOptions { ResourceVersion : "1" } ) ; err != nil {
t . Fatal ( err )
}
2016-05-28 22:10:25 +00:00
if e , a := 2 , len ( testHandler . actions ) ; e != a {
t . Errorf ( "expect %d requests, got %d" , e , a )
}
2017-01-09 21:21:23 +00:00
if e , a := "resourceVersion=1&watch=true" , testHandler . actions [ 0 ] . query ; e != a {
2016-05-28 22:10:25 +00:00
t . Errorf ( "expect %s, got %s" , e , a )
}
if e , a := "resourceVersion=1" , testHandler . actions [ 1 ] . query ; e != a {
t . Errorf ( "expect %s, got %s" , e , a )
}
}
2016-08-22 21:36:23 +00:00
func podToGCNode ( pod * v1 . Pod ) * node {
return & node {
identity : objectReference {
2016-12-04 03:42:29 +00:00
OwnerReference : metav1 . OwnerReference {
2016-08-22 21:36:23 +00:00
Kind : pod . Kind ,
APIVersion : pod . APIVersion ,
Name : pod . Name ,
UID : pod . UID ,
} ,
Namespace : pod . Namespace ,
} ,
2017-02-23 19:16:13 +00:00
// owners are intentionally left empty. The attemptToDeleteItem routine should get the latest item from the server.
2016-08-22 21:36:23 +00:00
owners : nil ,
}
}
func TestAbsentUIDCache ( t * testing . T ) {
2016-12-09 18:16:33 +00:00
rc1Pod1 := getPod ( "rc1Pod1" , [ ] metav1 . OwnerReference {
2016-08-22 21:36:23 +00:00
{
Kind : "ReplicationController" ,
Name : "rc1" ,
UID : "1" ,
APIVersion : "v1" ,
} ,
} )
2016-12-09 18:16:33 +00:00
rc1Pod2 := getPod ( "rc1Pod2" , [ ] metav1 . OwnerReference {
2016-08-22 21:36:23 +00:00
{
Kind : "ReplicationController" ,
Name : "rc1" ,
UID : "1" ,
APIVersion : "v1" ,
} ,
} )
2016-12-09 18:16:33 +00:00
rc2Pod1 := getPod ( "rc2Pod1" , [ ] metav1 . OwnerReference {
2016-08-22 21:36:23 +00:00
{
Kind : "ReplicationController" ,
Name : "rc2" ,
UID : "2" ,
APIVersion : "v1" ,
} ,
} )
2016-12-09 18:16:33 +00:00
rc3Pod1 := getPod ( "rc3Pod1" , [ ] metav1 . OwnerReference {
2016-08-22 21:36:23 +00:00
{
Kind : "ReplicationController" ,
Name : "rc3" ,
UID : "3" ,
APIVersion : "v1" ,
} ,
} )
testHandler := & fakeActionHandler {
response : map [ string ] FakeResponse {
"GET" + "/api/v1/namespaces/ns1/pods/rc1Pod1" : {
200 ,
serilizeOrDie ( t , rc1Pod1 ) ,
} ,
"GET" + "/api/v1/namespaces/ns1/pods/rc1Pod2" : {
200 ,
serilizeOrDie ( t , rc1Pod2 ) ,
} ,
"GET" + "/api/v1/namespaces/ns1/pods/rc2Pod1" : {
200 ,
serilizeOrDie ( t , rc2Pod1 ) ,
} ,
"GET" + "/api/v1/namespaces/ns1/pods/rc3Pod1" : {
200 ,
serilizeOrDie ( t , rc3Pod1 ) ,
} ,
"GET" + "/api/v1/namespaces/ns1/replicationcontrollers/rc1" : {
404 ,
[ ] byte { } ,
} ,
"GET" + "/api/v1/namespaces/ns1/replicationcontrollers/rc2" : {
404 ,
[ ] byte { } ,
} ,
"GET" + "/api/v1/namespaces/ns1/replicationcontrollers/rc3" : {
404 ,
[ ] byte { } ,
} ,
} ,
}
srv , clientConfig := testServerAndClientConfig ( testHandler . ServeHTTP )
defer srv . Close ( )
gc := setupGC ( t , clientConfig )
2017-05-04 17:55:24 +00:00
defer close ( gc . stop )
2016-08-22 21:36:23 +00:00
gc . absentOwnerCache = NewUIDCache ( 2 )
2017-02-23 19:16:13 +00:00
gc . attemptToDeleteItem ( podToGCNode ( rc1Pod1 ) )
gc . attemptToDeleteItem ( podToGCNode ( rc2Pod1 ) )
2016-08-22 21:36:23 +00:00
// rc1 should already be in the cache, no request should be sent. rc1 should be promoted in the UIDCache
2017-02-23 19:16:13 +00:00
gc . attemptToDeleteItem ( podToGCNode ( rc1Pod2 ) )
2016-08-22 21:36:23 +00:00
// after this call, rc2 should be evicted from the UIDCache
2017-02-23 19:16:13 +00:00
gc . attemptToDeleteItem ( podToGCNode ( rc3Pod1 ) )
2016-08-22 21:36:23 +00:00
// check cache
if ! gc . absentOwnerCache . Has ( types . UID ( "1" ) ) {
t . Errorf ( "expected rc1 to be in the cache" )
}
if gc . absentOwnerCache . Has ( types . UID ( "2" ) ) {
t . Errorf ( "expected rc2 to not exist in the cache" )
}
if ! gc . absentOwnerCache . Has ( types . UID ( "3" ) ) {
t . Errorf ( "expected rc3 to be in the cache" )
}
// check the request sent to the server
count := 0
for _ , action := range testHandler . actions {
if action . String ( ) == "GET=/api/v1/namespaces/ns1/replicationcontrollers/rc1" {
count ++
}
}
if count != 1 {
t . Errorf ( "expected only 1 GET rc1 request, got %d" , count )
}
}
2017-02-23 19:16:13 +00:00
func TestDeleteOwnerRefPatch ( t * testing . T ) {
original := v1 . Pod {
ObjectMeta : metav1 . ObjectMeta {
UID : "100" ,
OwnerReferences : [ ] metav1 . OwnerReference {
{ UID : "1" } ,
{ UID : "2" } ,
{ UID : "3" } ,
} ,
} ,
}
originalData := serilizeOrDie ( t , original )
expected := v1 . Pod {
ObjectMeta : metav1 . ObjectMeta {
UID : "100" ,
OwnerReferences : [ ] metav1 . OwnerReference {
{ UID : "1" } ,
} ,
} ,
}
patch := deleteOwnerRefPatch ( "100" , "2" , "3" )
patched , err := strategicpatch . StrategicMergePatch ( originalData , patch , v1 . Pod { } )
if err != nil {
t . Fatal ( err )
}
var got v1 . Pod
if err := json . Unmarshal ( patched , & got ) ; err != nil {
t . Fatal ( err )
}
if ! reflect . DeepEqual ( expected , got ) {
t . Errorf ( "expected: %#v,\ngot: %#v" , expected , got )
}
}
func TestUnblockOwnerReference ( t * testing . T ) {
trueVar := true
falseVar := false
original := v1 . Pod {
ObjectMeta : metav1 . ObjectMeta {
UID : "100" ,
OwnerReferences : [ ] metav1 . OwnerReference {
{ UID : "1" , BlockOwnerDeletion : & trueVar } ,
{ UID : "2" , BlockOwnerDeletion : & falseVar } ,
{ UID : "3" } ,
} ,
} ,
}
originalData := serilizeOrDie ( t , original )
expected := v1 . Pod {
ObjectMeta : metav1 . ObjectMeta {
UID : "100" ,
OwnerReferences : [ ] metav1 . OwnerReference {
{ UID : "1" , BlockOwnerDeletion : & falseVar } ,
{ UID : "2" , BlockOwnerDeletion : & falseVar } ,
{ UID : "3" } ,
} ,
} ,
}
accessor , err := meta . Accessor ( & original )
if err != nil {
t . Fatal ( err )
}
n := node {
owners : accessor . GetOwnerReferences ( ) ,
}
patch , err := n . patchToUnblockOwnerReferences ( )
if err != nil {
t . Fatal ( err )
}
patched , err := strategicpatch . StrategicMergePatch ( originalData , patch , v1 . Pod { } )
if err != nil {
t . Fatal ( err )
}
var got v1 . Pod
if err := json . Unmarshal ( patched , & got ) ; err != nil {
t . Fatal ( err )
}
if ! reflect . DeepEqual ( expected , got ) {
t . Errorf ( "expected: %#v,\ngot: %#v" , expected , got )
t . Errorf ( "expected: %#v,\ngot: %#v" , expected . OwnerReferences , got . OwnerReferences )
for _ , ref := range got . OwnerReferences {
t . Errorf ( "ref.UID=%s, ref.BlockOwnerDeletion=%v" , ref . UID , * ref . BlockOwnerDeletion )
}
}
}
2017-06-05 23:22:46 +00:00
func TestOrphanDependentsFailure ( t * testing . T ) {
testHandler := & fakeActionHandler {
response : map [ string ] FakeResponse {
"PATCH" + "/api/v1/namespaces/ns1/pods/pod" : {
409 ,
[ ] byte { } ,
} ,
} ,
}
srv , clientConfig := testServerAndClientConfig ( testHandler . ServeHTTP )
defer srv . Close ( )
gc := setupGC ( t , clientConfig )
defer close ( gc . stop )
dependents := [ ] * node {
{
identity : objectReference {
OwnerReference : metav1 . OwnerReference {
Kind : "Pod" ,
APIVersion : "v1" ,
Name : "pod" ,
} ,
Namespace : "ns1" ,
} ,
} ,
}
err := gc . orphanDependents ( objectReference { } , dependents )
expected := ` the server reported a conflict (patch pods pod) `
if err == nil || ! strings . Contains ( err . Error ( ) , expected ) {
t . Errorf ( "expected error contains text %s, got %v" , expected , err )
}
}
2017-11-07 18:19:43 +00:00
// TestGetDeletableResources ensures GetDeletableResources always returns
// something usable regardless of discovery output.
func TestGetDeletableResources ( t * testing . T ) {
tests := map [ string ] struct {
serverResources [ ] * metav1 . APIResourceList
err error
deletableResources map [ schema . GroupVersionResource ] struct { }
} {
"no error" : {
serverResources : [ ] * metav1 . APIResourceList {
{
// Valid GroupVersion
GroupVersion : "apps/v1" ,
APIResources : [ ] metav1 . APIResource {
2017-11-15 07:26:58 +00:00
{ Name : "pods" , Namespaced : true , Kind : "Pod" , Verbs : metav1 . Verbs { "delete" , "list" , "watch" } } ,
2017-11-07 18:19:43 +00:00
{ Name : "services" , Namespaced : true , Kind : "Service" } ,
} ,
} ,
{
// Invalid GroupVersion, should be ignored
GroupVersion : "foo//whatever" ,
APIResources : [ ] metav1 . APIResource {
2017-11-15 07:26:58 +00:00
{ Name : "bars" , Namespaced : true , Kind : "Bar" , Verbs : metav1 . Verbs { "delete" , "list" , "watch" } } ,
} ,
} ,
{
// Valid GroupVersion, missing required verbs, should be ignored
GroupVersion : "acme/v1" ,
APIResources : [ ] metav1 . APIResource {
{ Name : "widgets" , Namespaced : true , Kind : "Widget" , Verbs : metav1 . Verbs { "delete" } } ,
2017-11-07 18:19:43 +00:00
} ,
} ,
} ,
err : nil ,
deletableResources : map [ schema . GroupVersionResource ] struct { } {
{ Group : "apps" , Version : "v1" , Resource : "pods" } : { } ,
} ,
} ,
"nonspecific failure, includes usable results" : {
serverResources : [ ] * metav1 . APIResourceList {
{
GroupVersion : "apps/v1" ,
APIResources : [ ] metav1 . APIResource {
2017-11-15 07:26:58 +00:00
{ Name : "pods" , Namespaced : true , Kind : "Pod" , Verbs : metav1 . Verbs { "delete" , "list" , "watch" } } ,
2017-11-07 18:19:43 +00:00
{ Name : "services" , Namespaced : true , Kind : "Service" } ,
} ,
} ,
} ,
err : fmt . Errorf ( "internal error" ) ,
deletableResources : map [ schema . GroupVersionResource ] struct { } {
{ Group : "apps" , Version : "v1" , Resource : "pods" } : { } ,
} ,
} ,
"partial discovery failure, includes usable results" : {
serverResources : [ ] * metav1 . APIResourceList {
{
GroupVersion : "apps/v1" ,
APIResources : [ ] metav1 . APIResource {
2017-11-15 07:26:58 +00:00
{ Name : "pods" , Namespaced : true , Kind : "Pod" , Verbs : metav1 . Verbs { "delete" , "list" , "watch" } } ,
2017-11-07 18:19:43 +00:00
{ Name : "services" , Namespaced : true , Kind : "Service" } ,
} ,
} ,
} ,
err : & discovery . ErrGroupDiscoveryFailed {
Groups : map [ schema . GroupVersion ] error {
{ Group : "foo" , Version : "v1" } : fmt . Errorf ( "discovery failure" ) ,
} ,
} ,
deletableResources : map [ schema . GroupVersionResource ] struct { } {
{ Group : "apps" , Version : "v1" , Resource : "pods" } : { } ,
} ,
} ,
"discovery failure, no results" : {
serverResources : nil ,
err : fmt . Errorf ( "internal error" ) ,
deletableResources : map [ schema . GroupVersionResource ] struct { } { } ,
} ,
}
for name , test := range tests {
t . Logf ( "testing %q" , name )
client := & fakeServerResources {
PreferredResources : test . serverResources ,
Error : test . err ,
}
actual := GetDeletableResources ( client )
if ! reflect . DeepEqual ( test . deletableResources , actual ) {
t . Errorf ( "expected resources:\n%v\ngot:\n%v" , test . deletableResources , actual )
}
}
}
type fakeServerResources struct {
PreferredResources [ ] * metav1 . APIResourceList
Error error
}
func ( _ * fakeServerResources ) ServerResourcesForGroupVersion ( groupVersion string ) ( * metav1 . APIResourceList , error ) {
return nil , nil
}
func ( _ * fakeServerResources ) ServerResources ( ) ( [ ] * metav1 . APIResourceList , error ) {
return nil , nil
}
func ( f * fakeServerResources ) ServerPreferredResources ( ) ( [ ] * metav1 . APIResourceList , error ) {
return f . PreferredResources , f . Error
}
func ( _ * fakeServerResources ) ServerPreferredNamespacedResources ( ) ( [ ] * metav1 . APIResourceList , error ) {
return nil , nil
}