2015-09-10 21:32:57 +00:00
/ *
2016-06-03 00:25:58 +00:00
Copyright 2014 The Kubernetes Authors .
2015-09-10 21:32:57 +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 cmd
import (
"bytes"
"encoding/json"
2016-06-09 04:14:17 +00:00
"fmt"
2015-09-10 21:32:57 +00:00
"io/ioutil"
"net/http"
"os"
"testing"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/api"
2016-05-05 00:37:03 +00:00
"k8s.io/kubernetes/pkg/api/annotations"
2016-06-09 04:14:17 +00:00
kubeerr "k8s.io/kubernetes/pkg/api/errors"
2016-03-25 08:57:45 +00:00
"k8s.io/kubernetes/pkg/api/meta"
2016-08-23 11:29:57 +00:00
"k8s.io/kubernetes/pkg/api/testapi"
2016-10-28 00:01:14 +00:00
"k8s.io/kubernetes/pkg/client/restclient/fake"
2016-10-18 22:53:26 +00:00
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
2015-09-10 21:32:57 +00:00
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/runtime"
2016-11-21 02:55:31 +00:00
"k8s.io/kubernetes/pkg/runtime/schema"
2015-09-10 21:32:57 +00:00
)
func TestApplyExtraArgsFail ( t * testing . T ) {
buf := bytes . NewBuffer ( [ ] byte { } )
2016-11-11 22:38:36 +00:00
errBuf := bytes . NewBuffer ( [ ] byte { } )
2015-09-10 21:32:57 +00:00
2016-10-18 22:53:26 +00:00
f , _ , _ , _ := cmdtesting . NewAPIFactory ( )
2016-11-11 22:38:36 +00:00
c := NewCmdApply ( f , buf , errBuf )
2015-09-10 21:32:57 +00:00
if validateApplyArgs ( c , [ ] string { "rc" } ) == nil {
t . Fatalf ( "unexpected non-error" )
}
}
func validateApplyArgs ( cmd * cobra . Command , args [ ] string ) error {
if len ( args ) != 0 {
return cmdutil . UsageError ( cmd , "Unexpected args: %v" , args )
}
return nil
}
const (
2016-02-10 00:07:02 +00:00
filenameRC = "../../../test/fixtures/pkg/kubectl/cmd/apply/rc.yaml"
filenameSVC = "../../../test/fixtures/pkg/kubectl/cmd/apply/service.yaml"
filenameRCSVC = "../../../test/fixtures/pkg/kubectl/cmd/apply/rc-service.yaml"
2015-09-10 21:32:57 +00:00
)
func readBytesFromFile ( t * testing . T , filename string ) [ ] byte {
file , err := os . Open ( filename )
if err != nil {
t . Fatal ( err )
}
2016-09-06 07:58:22 +00:00
defer file . Close ( )
2015-09-10 21:32:57 +00:00
data , err := ioutil . ReadAll ( file )
if err != nil {
t . Fatal ( err )
}
return data
}
2016-11-11 22:38:36 +00:00
func readReplicationController ( t * testing . T , filenameRC string ) ( string , [ ] byte ) {
rcObj := readReplicationControllerFromFile ( t , filenameRC )
metaAccessor , err := meta . Accessor ( rcObj )
if err != nil {
t . Fatal ( err )
}
rcBytes , err := runtime . Encode ( testapi . Default . Codec ( ) , rcObj )
if err != nil {
t . Fatal ( err )
}
return metaAccessor . GetName ( ) , rcBytes
}
2015-09-10 21:32:57 +00:00
func readReplicationControllerFromFile ( t * testing . T , filename string ) * api . ReplicationController {
data := readBytesFromFile ( t , filename )
rc := api . ReplicationController { }
2016-08-23 11:29:57 +00:00
if err := runtime . DecodeInto ( testapi . Default . Codec ( ) , data , & rc ) ; err != nil {
2015-09-10 21:32:57 +00:00
t . Fatal ( err )
}
return & rc
}
func readServiceFromFile ( t * testing . T , filename string ) * api . Service {
data := readBytesFromFile ( t , filename )
svc := api . Service { }
2016-08-23 11:29:57 +00:00
if err := runtime . DecodeInto ( testapi . Default . Codec ( ) , data , & svc ) ; err != nil {
2015-09-10 21:32:57 +00:00
t . Fatal ( err )
}
return & svc
}
func annotateRuntimeObject ( t * testing . T , originalObj , currentObj runtime . Object , kind string ) ( string , [ ] byte ) {
2016-03-25 08:57:45 +00:00
originalAccessor , err := meta . Accessor ( originalObj )
2015-09-10 21:32:57 +00:00
if err != nil {
t . Fatal ( err )
}
2016-03-25 08:57:45 +00:00
originalLabels := originalAccessor . GetLabels ( )
originalLabels [ "DELETE_ME" ] = "DELETE_ME"
originalAccessor . SetLabels ( originalLabels )
2016-08-23 11:29:57 +00:00
original , err := runtime . Encode ( testapi . Default . Codec ( ) , originalObj )
2015-09-10 21:32:57 +00:00
if err != nil {
t . Fatal ( err )
}
2016-03-25 08:57:45 +00:00
currentAccessor , err := meta . Accessor ( currentObj )
2015-09-10 21:32:57 +00:00
if err != nil {
t . Fatal ( err )
}
2016-03-25 08:57:45 +00:00
currentAnnotations := currentAccessor . GetAnnotations ( )
if currentAnnotations == nil {
currentAnnotations = make ( map [ string ] string )
2015-09-10 21:32:57 +00:00
}
2016-05-05 00:37:03 +00:00
currentAnnotations [ annotations . LastAppliedConfigAnnotation ] = string ( original )
2016-03-25 08:57:45 +00:00
currentAccessor . SetAnnotations ( currentAnnotations )
2016-08-23 11:29:57 +00:00
current , err := runtime . Encode ( testapi . Default . Codec ( ) , currentObj )
2015-09-10 21:32:57 +00:00
if err != nil {
t . Fatal ( err )
}
2016-03-25 08:57:45 +00:00
return currentAccessor . GetName ( ) , current
2015-09-10 21:32:57 +00:00
}
func readAndAnnotateReplicationController ( t * testing . T , filename string ) ( string , [ ] byte ) {
rc1 := readReplicationControllerFromFile ( t , filename )
rc2 := readReplicationControllerFromFile ( t , filename )
return annotateRuntimeObject ( t , rc1 , rc2 , "ReplicationController" )
}
func readAndAnnotateService ( t * testing . T , filename string ) ( string , [ ] byte ) {
svc1 := readServiceFromFile ( t , filename )
svc2 := readServiceFromFile ( t , filename )
return annotateRuntimeObject ( t , svc1 , svc2 , "Service" )
}
func validatePatchApplication ( t * testing . T , req * http . Request ) {
patch , err := ioutil . ReadAll ( req . Body )
if err != nil {
t . Fatal ( err )
}
patchMap := map [ string ] interface { } { }
if err := json . Unmarshal ( patch , & patchMap ) ; err != nil {
t . Fatal ( err )
}
annotationsMap := walkMapPath ( t , patchMap , [ ] string { "metadata" , "annotations" } )
2016-05-05 00:37:03 +00:00
if _ , ok := annotationsMap [ annotations . LastAppliedConfigAnnotation ] ; ! ok {
2015-09-10 21:32:57 +00:00
t . Fatalf ( "patch does not contain annotation:\n%s\n" , patch )
}
labelMap := walkMapPath ( t , patchMap , [ ] string { "metadata" , "labels" } )
if deleteMe , ok := labelMap [ "DELETE_ME" ] ; ! ok || deleteMe != nil {
t . Fatalf ( "patch does not remove deleted key: DELETE_ME:\n%s\n" , patch )
}
}
func walkMapPath ( t * testing . T , start map [ string ] interface { } , path [ ] string ) map [ string ] interface { } {
finish := start
for i := 0 ; i < len ( path ) ; i ++ {
var ok bool
finish , ok = finish [ path [ i ] ] . ( map [ string ] interface { } )
if ! ok {
t . Fatalf ( "key:%s of path:%v not found in map:%v" , path [ i ] , path , start )
}
}
return finish
}
2016-11-11 22:38:36 +00:00
func TestApplyObjectWithoutAnnotation ( t * testing . T ) {
initTestErrorHandler ( t )
nameRC , rcBytes := readReplicationController ( t , filenameRC )
pathRC := "/namespaces/test/replicationcontrollers/" + nameRC
f , tf , _ , ns := cmdtesting . NewAPIFactory ( )
tf . Printer = & testPrinter { }
tf . Client = & fake . RESTClient {
NegotiatedSerializer : ns ,
Client : fake . CreateHTTPClient ( func ( req * http . Request ) ( * http . Response , error ) {
switch p , m := req . URL . Path , req . Method ; {
case p == pathRC && m == "GET" :
bodyRC := ioutil . NopCloser ( bytes . NewReader ( rcBytes ) )
return & http . Response { StatusCode : 200 , Header : defaultHeader ( ) , Body : bodyRC } , nil
case p == pathRC && m == "PATCH" :
bodyRC := ioutil . NopCloser ( bytes . NewReader ( rcBytes ) )
return & http . Response { StatusCode : 200 , Header : defaultHeader ( ) , Body : bodyRC } , nil
default :
t . Fatalf ( "unexpected request: %#v\n%#v" , req . URL , req )
return nil , nil
}
} ) ,
}
tf . Namespace = "test"
tf . ClientConfig = defaultClientConfig ( )
buf := bytes . NewBuffer ( [ ] byte { } )
errBuf := bytes . NewBuffer ( [ ] byte { } )
cmd := NewCmdApply ( f , buf , errBuf )
cmd . Flags ( ) . Set ( "filename" , filenameRC )
cmd . Flags ( ) . Set ( "output" , "name" )
cmd . Run ( cmd , [ ] string { } )
// uses the name from the file, not the response
expectRC := "replicationcontroller/" + nameRC + "\n"
expectWarning := warningNoLastAppliedConfigAnnotation
if errBuf . String ( ) != expectWarning {
t . Fatalf ( "unexpected non-warning: %s\nexpected: %s" , errBuf . String ( ) , expectWarning )
}
if buf . String ( ) != expectRC {
t . Fatalf ( "unexpected output: %s\nexpected: %s" , buf . String ( ) , expectRC )
}
}
2015-09-10 21:32:57 +00:00
func TestApplyObject ( t * testing . T ) {
2015-12-02 20:20:10 +00:00
initTestErrorHandler ( t )
2015-09-10 21:32:57 +00:00
nameRC , currentRC := readAndAnnotateReplicationController ( t , filenameRC )
pathRC := "/namespaces/test/replicationcontrollers/" + nameRC
2016-10-18 22:53:26 +00:00
f , tf , _ , ns := cmdtesting . NewAPIFactory ( )
2015-09-10 21:32:57 +00:00
tf . Printer = & testPrinter { }
tf . Client = & fake . RESTClient {
2016-05-04 10:24:16 +00:00
NegotiatedSerializer : ns ,
2015-11-11 19:54:58 +00:00
Client : fake . CreateHTTPClient ( func ( req * http . Request ) ( * http . Response , error ) {
2015-09-10 21:32:57 +00:00
switch p , m := req . URL . Path , req . Method ; {
case p == pathRC && m == "GET" :
bodyRC := ioutil . NopCloser ( bytes . NewReader ( currentRC ) )
2016-05-11 07:50:47 +00:00
return & http . Response { StatusCode : 200 , Header : defaultHeader ( ) , Body : bodyRC } , nil
2015-09-10 21:32:57 +00:00
case p == pathRC && m == "PATCH" :
validatePatchApplication ( t , req )
bodyRC := ioutil . NopCloser ( bytes . NewReader ( currentRC ) )
2016-05-11 07:50:47 +00:00
return & http . Response { StatusCode : 200 , Header : defaultHeader ( ) , Body : bodyRC } , nil
2015-09-10 21:32:57 +00:00
default :
t . Fatalf ( "unexpected request: %#v\n%#v" , req . URL , req )
return nil , nil
}
} ) ,
}
tf . Namespace = "test"
buf := bytes . NewBuffer ( [ ] byte { } )
2016-11-11 22:38:36 +00:00
errBuf := bytes . NewBuffer ( [ ] byte { } )
2015-09-10 21:32:57 +00:00
2016-11-11 22:38:36 +00:00
cmd := NewCmdApply ( f , buf , errBuf )
2015-09-10 21:32:57 +00:00
cmd . Flags ( ) . Set ( "filename" , filenameRC )
cmd . Flags ( ) . Set ( "output" , "name" )
cmd . Run ( cmd , [ ] string { } )
// uses the name from the file, not the response
expectRC := "replicationcontroller/" + nameRC + "\n"
if buf . String ( ) != expectRC {
t . Fatalf ( "unexpected output: %s\nexpected: %s" , buf . String ( ) , expectRC )
}
}
2016-06-09 04:14:17 +00:00
func TestApplyRetry ( t * testing . T ) {
initTestErrorHandler ( t )
nameRC , currentRC := readAndAnnotateReplicationController ( t , filenameRC )
pathRC := "/namespaces/test/replicationcontrollers/" + nameRC
firstPatch := true
retry := false
getCount := 0
2016-10-18 22:53:26 +00:00
f , tf , _ , ns := cmdtesting . NewAPIFactory ( )
2016-06-09 04:14:17 +00:00
tf . Printer = & testPrinter { }
tf . Client = & fake . RESTClient {
2016-05-04 10:24:16 +00:00
NegotiatedSerializer : ns ,
2016-06-09 04:14:17 +00:00
Client : fake . CreateHTTPClient ( func ( req * http . Request ) ( * http . Response , error ) {
switch p , m := req . URL . Path , req . Method ; {
case p == pathRC && m == "GET" :
getCount ++
bodyRC := ioutil . NopCloser ( bytes . NewReader ( currentRC ) )
return & http . Response { StatusCode : 200 , Header : defaultHeader ( ) , Body : bodyRC } , nil
case p == pathRC && m == "PATCH" :
if firstPatch {
firstPatch = false
2016-11-21 02:55:31 +00:00
statusErr := kubeerr . NewConflict ( schema . GroupResource { Group : "" , Resource : "rc" } , "test-rc" , fmt . Errorf ( "the object has been modified. Please apply at first." ) )
2016-06-09 04:14:17 +00:00
bodyBytes , _ := json . Marshal ( statusErr )
bodyErr := ioutil . NopCloser ( bytes . NewReader ( bodyBytes ) )
return & http . Response { StatusCode : http . StatusConflict , Header : defaultHeader ( ) , Body : bodyErr } , nil
}
retry = true
validatePatchApplication ( t , req )
bodyRC := ioutil . NopCloser ( bytes . NewReader ( currentRC ) )
return & http . Response { StatusCode : 200 , Header : defaultHeader ( ) , Body : bodyRC } , nil
default :
t . Fatalf ( "unexpected request: %#v\n%#v" , req . URL , req )
return nil , nil
}
} ) ,
}
tf . Namespace = "test"
buf := bytes . NewBuffer ( [ ] byte { } )
2016-11-11 22:38:36 +00:00
errBuf := bytes . NewBuffer ( [ ] byte { } )
2016-06-09 04:14:17 +00:00
2016-11-11 22:38:36 +00:00
cmd := NewCmdApply ( f , buf , errBuf )
2016-06-09 04:14:17 +00:00
cmd . Flags ( ) . Set ( "filename" , filenameRC )
cmd . Flags ( ) . Set ( "output" , "name" )
cmd . Run ( cmd , [ ] string { } )
if ! retry || getCount != 2 {
t . Fatalf ( "apply didn't retry when get conflict error" )
}
// uses the name from the file, not the response
expectRC := "replicationcontroller/" + nameRC + "\n"
if buf . String ( ) != expectRC {
t . Fatalf ( "unexpected output: %s\nexpected: %s" , buf . String ( ) , expectRC )
}
}
2015-11-05 02:29:56 +00:00
func TestApplyNonExistObject ( t * testing . T ) {
nameRC , currentRC := readAndAnnotateReplicationController ( t , filenameRC )
pathRC := "/namespaces/test/replicationcontrollers"
pathNameRC := pathRC + "/" + nameRC
2016-10-18 22:53:26 +00:00
f , tf , _ , ns := cmdtesting . NewAPIFactory ( )
2015-11-05 02:29:56 +00:00
tf . Printer = & testPrinter { }
tf . Client = & fake . RESTClient {
2016-05-04 10:24:16 +00:00
NegotiatedSerializer : ns ,
2015-11-11 19:54:58 +00:00
Client : fake . CreateHTTPClient ( func ( req * http . Request ) ( * http . Response , error ) {
2015-11-05 02:29:56 +00:00
switch p , m := req . URL . Path , req . Method ; {
2016-08-30 03:57:59 +00:00
case p == "/api/v1/namespaces/test" && m == "GET" :
return & http . Response { StatusCode : 404 , Header : defaultHeader ( ) , Body : ioutil . NopCloser ( bytes . NewReader ( nil ) ) } , nil
2015-11-05 02:29:56 +00:00
case p == pathNameRC && m == "GET" :
2016-05-11 07:50:47 +00:00
return & http . Response { StatusCode : 404 , Header : defaultHeader ( ) , Body : ioutil . NopCloser ( bytes . NewReader ( nil ) ) } , nil
2015-11-05 02:29:56 +00:00
case p == pathRC && m == "POST" :
bodyRC := ioutil . NopCloser ( bytes . NewReader ( currentRC ) )
2016-05-11 07:50:47 +00:00
return & http . Response { StatusCode : 201 , Header : defaultHeader ( ) , Body : bodyRC } , nil
2015-11-05 02:29:56 +00:00
default :
t . Fatalf ( "unexpected request: %#v\n%#v" , req . URL , req )
return nil , nil
}
} ) ,
}
tf . Namespace = "test"
buf := bytes . NewBuffer ( [ ] byte { } )
2016-11-11 22:38:36 +00:00
errBuf := bytes . NewBuffer ( [ ] byte { } )
2015-11-05 02:29:56 +00:00
2016-11-11 22:38:36 +00:00
cmd := NewCmdApply ( f , buf , errBuf )
2015-11-05 02:29:56 +00:00
cmd . Flags ( ) . Set ( "filename" , filenameRC )
cmd . Flags ( ) . Set ( "output" , "name" )
cmd . Run ( cmd , [ ] string { } )
// uses the name from the file, not the response
expectRC := "replicationcontroller/" + nameRC + "\n"
if buf . String ( ) != expectRC {
t . Errorf ( "unexpected output: %s\nexpected: %s" , buf . String ( ) , expectRC )
}
}
2016-02-10 00:07:02 +00:00
func TestApplyMultipleObjectsAsList ( t * testing . T ) {
testApplyMultipleObjects ( t , true )
}
func TestApplyMultipleObjectsAsFiles ( t * testing . T ) {
testApplyMultipleObjects ( t , false )
}
func testApplyMultipleObjects ( t * testing . T , asList bool ) {
2015-09-10 21:32:57 +00:00
nameRC , currentRC := readAndAnnotateReplicationController ( t , filenameRC )
pathRC := "/namespaces/test/replicationcontrollers/" + nameRC
nameSVC , currentSVC := readAndAnnotateService ( t , filenameSVC )
pathSVC := "/namespaces/test/services/" + nameSVC
2016-10-18 22:53:26 +00:00
f , tf , _ , ns := cmdtesting . NewAPIFactory ( )
2015-09-10 21:32:57 +00:00
tf . Printer = & testPrinter { }
tf . Client = & fake . RESTClient {
2016-05-04 10:24:16 +00:00
NegotiatedSerializer : ns ,
2015-11-11 19:54:58 +00:00
Client : fake . CreateHTTPClient ( func ( req * http . Request ) ( * http . Response , error ) {
2015-09-10 21:32:57 +00:00
switch p , m := req . URL . Path , req . Method ; {
case p == pathRC && m == "GET" :
bodyRC := ioutil . NopCloser ( bytes . NewReader ( currentRC ) )
2016-05-11 07:50:47 +00:00
return & http . Response { StatusCode : 200 , Header : defaultHeader ( ) , Body : bodyRC } , nil
2015-09-10 21:32:57 +00:00
case p == pathRC && m == "PATCH" :
validatePatchApplication ( t , req )
bodyRC := ioutil . NopCloser ( bytes . NewReader ( currentRC ) )
2016-05-11 07:50:47 +00:00
return & http . Response { StatusCode : 200 , Header : defaultHeader ( ) , Body : bodyRC } , nil
2015-09-10 21:32:57 +00:00
case p == pathSVC && m == "GET" :
bodySVC := ioutil . NopCloser ( bytes . NewReader ( currentSVC ) )
2016-05-11 07:50:47 +00:00
return & http . Response { StatusCode : 200 , Header : defaultHeader ( ) , Body : bodySVC } , nil
2015-09-10 21:32:57 +00:00
case p == pathSVC && m == "PATCH" :
validatePatchApplication ( t , req )
bodySVC := ioutil . NopCloser ( bytes . NewReader ( currentSVC ) )
2016-05-11 07:50:47 +00:00
return & http . Response { StatusCode : 200 , Header : defaultHeader ( ) , Body : bodySVC } , nil
2015-09-10 21:32:57 +00:00
default :
t . Fatalf ( "unexpected request: %#v\n%#v" , req . URL , req )
return nil , nil
}
} ) ,
}
tf . Namespace = "test"
buf := bytes . NewBuffer ( [ ] byte { } )
2016-11-11 22:38:36 +00:00
errBuf := bytes . NewBuffer ( [ ] byte { } )
2015-09-10 21:32:57 +00:00
2016-11-11 22:38:36 +00:00
cmd := NewCmdApply ( f , buf , errBuf )
2016-02-10 00:07:02 +00:00
if asList {
cmd . Flags ( ) . Set ( "filename" , filenameRCSVC )
} else {
cmd . Flags ( ) . Set ( "filename" , filenameRC )
cmd . Flags ( ) . Set ( "filename" , filenameSVC )
}
2015-09-10 21:32:57 +00:00
cmd . Flags ( ) . Set ( "output" , "name" )
cmd . Run ( cmd , [ ] string { } )
// Names should come from the REST response, NOT the files
expectRC := "replicationcontroller/" + nameRC + "\n"
expectSVC := "service/" + nameSVC + "\n"
2016-02-10 00:07:02 +00:00
// Test both possible orders since output is non-deterministic.
expectOne := expectRC + expectSVC
expectTwo := expectSVC + expectRC
if buf . String ( ) != expectOne && buf . String ( ) != expectTwo {
t . Fatalf ( "unexpected output: %s\nexpected: %s OR %s" , buf . String ( ) , expectOne , expectTwo )
2015-09-10 21:32:57 +00:00
}
}