2017-02-01 00:45:59 +00:00
/ *
Copyright 2014 The Kubernetes Authors .
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 handlers
import (
"encoding/hex"
"encoding/json"
"fmt"
"io/ioutil"
"math/rand"
"net/http"
"net/url"
"strings"
"time"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
2017-02-03 13:41:32 +00:00
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
2017-02-01 00:45:59 +00:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2017-02-03 13:41:32 +00:00
"k8s.io/apimachinery/pkg/fields"
2017-02-01 00:45:59 +00:00
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
2017-02-03 13:41:32 +00:00
"k8s.io/apimachinery/pkg/types"
2017-02-01 00:45:59 +00:00
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
2017-02-03 13:41:32 +00:00
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
"k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/kubernetes/pkg/genericapiserver/endpoints/handlers/responsewriters"
"k8s.io/kubernetes/pkg/genericapiserver/registry/rest"
2017-02-01 00:45:59 +00:00
"k8s.io/kubernetes/pkg/util"
"k8s.io/kubernetes/pkg/util/strategicpatch"
"github.com/emicklei/go-restful"
"github.com/golang/glog"
)
// ContextFunc returns a Context given a request - a context must be returned
type ContextFunc func ( req * restful . Request ) request . Context
// ScopeNamer handles accessing names from requests and objects
type ScopeNamer interface {
// Namespace returns the appropriate namespace value from the request (may be empty) or an
// error.
Namespace ( req * restful . Request ) ( namespace string , err error )
// Name returns the name from the request, and an optional namespace value if this is a namespace
// scoped call. An error is returned if the name is not available.
Name ( req * restful . Request ) ( namespace , name string , err error )
// ObjectName returns the namespace and name from an object if they exist, or an error if the object
// does not support names.
ObjectName ( obj runtime . Object ) ( namespace , name string , err error )
// SetSelfLink sets the provided URL onto the object. The method should return nil if the object
// does not support selfLinks.
SetSelfLink ( obj runtime . Object , url string ) error
// GenerateLink creates an encoded URI for a given runtime object that represents the canonical path
// and query.
GenerateLink ( req * restful . Request , obj runtime . Object ) ( uri string , err error )
// GenerateLink creates an encoded URI for a list that represents the canonical path and query.
GenerateListLink ( req * restful . Request ) ( uri string , err error )
}
// RequestScope encapsulates common fields across all RESTful handler methods.
type RequestScope struct {
Namer ScopeNamer
ContextFunc
Serializer runtime . NegotiatedSerializer
runtime . ParameterCodec
Creater runtime . ObjectCreater
Convertor runtime . ObjectConvertor
Copier runtime . ObjectCopier
Resource schema . GroupVersionResource
Kind schema . GroupVersionKind
Subresource string
2017-02-03 13:41:32 +00:00
MetaGroupVersion schema . GroupVersion
2017-02-01 00:45:59 +00:00
}
func ( scope * RequestScope ) err ( err error , w http . ResponseWriter , req * http . Request ) {
responsewriters . ErrorNegotiated ( err , scope . Serializer , scope . Kind . GroupVersion ( ) , w , req )
}
// getterFunc performs a get request with the given context and object name. The request
// may be used to deserialize an options object to pass to the getter.
type getterFunc func ( ctx request . Context , name string , req * restful . Request ) ( runtime . Object , error )
// maxRetryWhenPatchConflicts is the maximum number of conflicts retry during a patch operation before returning failure
const maxRetryWhenPatchConflicts = 5
// getResourceHandler is an HTTP handler function for get requests. It delegates to the
// passed-in getterFunc to perform the actual get.
func getResourceHandler ( scope RequestScope , getter getterFunc ) restful . RouteFunction {
return func ( req * restful . Request , res * restful . Response ) {
w := res . ResponseWriter
namespace , name , err := scope . Namer . Name ( req )
if err != nil {
scope . err ( err , res . ResponseWriter , req . Request )
return
}
ctx := scope . ContextFunc ( req )
ctx = request . WithNamespace ( ctx , namespace )
result , err := getter ( ctx , name , req )
if err != nil {
scope . err ( err , res . ResponseWriter , req . Request )
return
}
if err := setSelfLink ( result , req , scope . Namer ) ; err != nil {
scope . err ( err , res . ResponseWriter , req . Request )
return
}
responsewriters . WriteObject ( http . StatusOK , scope . Kind . GroupVersion ( ) , scope . Serializer , result , w , req . Request )
}
}
// GetResource returns a function that handles retrieving a single resource from a rest.Storage object.
func GetResource ( r rest . Getter , e rest . Exporter , scope RequestScope ) restful . RouteFunction {
return getResourceHandler ( scope ,
func ( ctx request . Context , name string , req * restful . Request ) ( runtime . Object , error ) {
// For performance tracking purposes.
trace := util . NewTrace ( "Get " + req . Request . URL . Path )
defer trace . LogIfLong ( 500 * time . Millisecond )
// check for export
options := metav1 . GetOptions { }
if values := req . Request . URL . Query ( ) ; len ( values ) > 0 {
exports := metav1 . ExportOptions { }
if err := scope . ParameterCodec . DecodeParameters ( values , schema . GroupVersion { Version : "v1" } , & exports ) ; err != nil {
return nil , err
}
if exports . Export {
if e == nil {
return nil , errors . NewBadRequest ( fmt . Sprintf ( "export of %q is not supported" , scope . Resource . Resource ) )
}
return e . Export ( ctx , name , exports )
}
if err := scope . ParameterCodec . DecodeParameters ( values , schema . GroupVersion { Version : "v1" } , & options ) ; err != nil {
return nil , err
}
}
return r . Get ( ctx , name , & options )
} )
}
// GetResourceWithOptions returns a function that handles retrieving a single resource from a rest.Storage object.
func GetResourceWithOptions ( r rest . GetterWithOptions , scope RequestScope ) restful . RouteFunction {
return getResourceHandler ( scope ,
func ( ctx request . Context , name string , req * restful . Request ) ( runtime . Object , error ) {
opts , subpath , subpathKey := r . NewGetOptions ( )
if err := getRequestOptions ( req , scope , opts , subpath , subpathKey ) ; err != nil {
return nil , err
}
return r . Get ( ctx , name , opts )
} )
}
func getRequestOptions ( req * restful . Request , scope RequestScope , into runtime . Object , subpath bool , subpathKey string ) error {
if into == nil {
return nil
}
query := req . Request . URL . Query ( )
if subpath {
newQuery := make ( url . Values )
for k , v := range query {
newQuery [ k ] = v
}
newQuery [ subpathKey ] = [ ] string { req . PathParameter ( "path" ) }
query = newQuery
}
return scope . ParameterCodec . DecodeParameters ( query , scope . Kind . GroupVersion ( ) , into )
}
// ConnectResource returns a function that handles a connect request on a rest.Storage object.
func ConnectResource ( connecter rest . Connecter , scope RequestScope , admit admission . Interface , restPath string ) restful . RouteFunction {
return func ( req * restful . Request , res * restful . Response ) {
w := res . ResponseWriter
namespace , name , err := scope . Namer . Name ( req )
if err != nil {
scope . err ( err , res . ResponseWriter , req . Request )
return
}
ctx := scope . ContextFunc ( req )
ctx = request . WithNamespace ( ctx , namespace )
opts , subpath , subpathKey := connecter . NewConnectOptions ( )
if err := getRequestOptions ( req , scope , opts , subpath , subpathKey ) ; err != nil {
scope . err ( err , res . ResponseWriter , req . Request )
return
}
if admit . Handles ( admission . Connect ) {
connectRequest := & rest . ConnectRequest {
Name : name ,
Options : opts ,
ResourcePath : restPath ,
}
userInfo , _ := request . UserFrom ( ctx )
err = admit . Admit ( admission . NewAttributesRecord ( connectRequest , nil , scope . Kind , namespace , name , scope . Resource , scope . Subresource , admission . Connect , userInfo ) )
if err != nil {
scope . err ( err , res . ResponseWriter , req . Request )
return
}
}
handler , err := connecter . Connect ( ctx , name , opts , & responder { scope : scope , req : req , res : res } )
if err != nil {
scope . err ( err , res . ResponseWriter , req . Request )
return
}
handler . ServeHTTP ( w , req . Request )
}
}
// responder implements rest.Responder for assisting a connector in writing objects or errors.
type responder struct {
scope RequestScope
req * restful . Request
res * restful . Response
}
func ( r * responder ) Object ( statusCode int , obj runtime . Object ) {
responsewriters . WriteObject ( statusCode , r . scope . Kind . GroupVersion ( ) , r . scope . Serializer , obj , r . res . ResponseWriter , r . req . Request )
}
func ( r * responder ) Error ( err error ) {
r . scope . err ( err , r . res . ResponseWriter , r . req . Request )
}
// ListResource returns a function that handles retrieving a list of resources from a rest.Storage object.
func ListResource ( r rest . Lister , rw rest . Watcher , scope RequestScope , forceWatch bool , minRequestTimeout time . Duration ) restful . RouteFunction {
return func ( req * restful . Request , res * restful . Response ) {
// For performance tracking purposes.
trace := util . NewTrace ( "List " + req . Request . URL . Path )
w := res . ResponseWriter
namespace , err := scope . Namer . Namespace ( req )
if err != nil {
scope . err ( err , res . ResponseWriter , req . Request )
return
}
// Watches for single objects are routed to this function.
// Treat a /name parameter the same as a field selector entry.
hasName := true
_ , name , err := scope . Namer . Name ( req )
if err != nil {
hasName = false
}
ctx := scope . ContextFunc ( req )
ctx = request . WithNamespace ( ctx , namespace )
2017-02-03 13:41:32 +00:00
opts := metainternalversion . ListOptions { }
if err := metainternalversion . ParameterCodec . DecodeParameters ( req . Request . URL . Query ( ) , scope . MetaGroupVersion , & opts ) ; err != nil {
2017-02-01 00:45:59 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
return
}
// transform fields
// TODO: DecodeParametersInto should do this.
if opts . FieldSelector != nil {
fn := func ( label , value string ) ( newLabel , newValue string , err error ) {
return scope . Convertor . ConvertFieldLabel ( scope . Kind . GroupVersion ( ) . String ( ) , scope . Kind . Kind , label , value )
}
if opts . FieldSelector , err = opts . FieldSelector . Transform ( fn ) ; err != nil {
// TODO: allow bad request to set field causes based on query parameters
err = errors . NewBadRequest ( err . Error ( ) )
scope . err ( err , res . ResponseWriter , req . Request )
return
}
}
if hasName {
// metadata.name is the canonical internal name.
// SelectionPredicate will notice that this is
// a request for a single object and optimize the
// storage query accordingly.
nameSelector := fields . OneTermEqualSelector ( "metadata.name" , name )
if opts . FieldSelector != nil && ! opts . FieldSelector . Empty ( ) {
// It doesn't make sense to ask for both a name
// and a field selector, since just the name is
// sufficient to narrow down the request to a
// single object.
scope . err ( errors . NewBadRequest ( "both a name and a field selector provided; please provide one or the other." ) , res . ResponseWriter , req . Request )
return
}
opts . FieldSelector = nameSelector
}
if ( opts . Watch || forceWatch ) && rw != nil {
2017-02-03 13:41:32 +00:00
glog . Infof ( "Started to log from %v for %v" , ctx , req . Request . URL . RequestURI ( ) )
2017-02-01 00:45:59 +00:00
watcher , err := rw . Watch ( ctx , & opts )
if err != nil {
scope . err ( err , res . ResponseWriter , req . Request )
return
}
// TODO: Currently we explicitly ignore ?timeout= and use only ?timeoutSeconds=.
timeout := time . Duration ( 0 )
if opts . TimeoutSeconds != nil {
timeout = time . Duration ( * opts . TimeoutSeconds ) * time . Second
}
if timeout == 0 && minRequestTimeout > 0 {
timeout = time . Duration ( float64 ( minRequestTimeout ) * ( rand . Float64 ( ) + 1.0 ) )
}
serveWatch ( watcher , scope , req , res , timeout )
return
}
// Log only long List requests (ignore Watch).
defer trace . LogIfLong ( 500 * time . Millisecond )
trace . Step ( "About to List from storage" )
result , err := r . List ( ctx , & opts )
if err != nil {
scope . err ( err , res . ResponseWriter , req . Request )
return
}
trace . Step ( "Listing from storage done" )
numberOfItems , err := setListSelfLink ( result , req , scope . Namer )
if err != nil {
scope . err ( err , res . ResponseWriter , req . Request )
return
}
trace . Step ( "Self-linking done" )
// Ensure empty lists return a non-nil items slice
if numberOfItems == 0 && meta . IsListType ( result ) {
if err := meta . SetList ( result , [ ] runtime . Object { } ) ; err != nil {
scope . err ( err , res . ResponseWriter , req . Request )
return
}
}
responsewriters . WriteObject ( http . StatusOK , scope . Kind . GroupVersion ( ) , scope . Serializer , result , w , req . Request )
trace . Step ( fmt . Sprintf ( "Writing http response done (%d items)" , numberOfItems ) )
}
}
func createHandler ( r rest . NamedCreater , scope RequestScope , typer runtime . ObjectTyper , admit admission . Interface , includeName bool ) restful . RouteFunction {
return func ( req * restful . Request , res * restful . Response ) {
// For performance tracking purposes.
trace := util . NewTrace ( "Create " + req . Request . URL . Path )
defer trace . LogIfLong ( 500 * time . Millisecond )
w := res . ResponseWriter
// TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer)
timeout := parseTimeout ( req . Request . URL . Query ( ) . Get ( "timeout" ) )
var (
namespace , name string
err error
)
if includeName {
namespace , name , err = scope . Namer . Name ( req )
} else {
namespace , err = scope . Namer . Namespace ( req )
}
if err != nil {
scope . err ( err , res . ResponseWriter , req . Request )
return
}
ctx := scope . ContextFunc ( req )
ctx = request . WithNamespace ( ctx , namespace )
gv := scope . Kind . GroupVersion ( )
s , err := negotiation . NegotiateInputSerializer ( req . Request , scope . Serializer )
if err != nil {
scope . err ( err , res . ResponseWriter , req . Request )
return
}
decoder := scope . Serializer . DecoderToVersion ( s . Serializer , schema . GroupVersion { Group : gv . Group , Version : runtime . APIVersionInternal } )
body , err := readBody ( req . Request )
if err != nil {
scope . err ( err , res . ResponseWriter , req . Request )
return
}
defaultGVK := scope . Kind
original := r . New ( )
trace . Step ( "About to convert to expected version" )
obj , gvk , err := decoder . Decode ( body , & defaultGVK , original )
if err != nil {
err = transformDecodeError ( typer , err , original , gvk , body )
scope . err ( err , res . ResponseWriter , req . Request )
return
}
if gvk . GroupVersion ( ) != gv {
err = errors . NewBadRequest ( fmt . Sprintf ( "the API version in the data (%s) does not match the expected API version (%v)" , gvk . GroupVersion ( ) . String ( ) , gv . String ( ) ) )
scope . err ( err , res . ResponseWriter , req . Request )
return
}
trace . Step ( "Conversion done" )
if admit != nil && admit . Handles ( admission . Create ) {
userInfo , _ := request . UserFrom ( ctx )
err = admit . Admit ( admission . NewAttributesRecord ( obj , nil , scope . Kind , namespace , name , scope . Resource , scope . Subresource , admission . Create , userInfo ) )
if err != nil {
scope . err ( err , res . ResponseWriter , req . Request )
return
}
}
trace . Step ( "About to store object in database" )
result , err := finishRequest ( timeout , func ( ) ( runtime . Object , error ) {
out , err := r . Create ( ctx , name , obj )
if status , ok := out . ( * metav1 . Status ) ; ok && err == nil && status . Code == 0 {
status . Code = http . StatusCreated
}
return out , err
} )
if err != nil {
scope . err ( err , res . ResponseWriter , req . Request )
return
}
trace . Step ( "Object stored in database" )
if err := setSelfLink ( result , req , scope . Namer ) ; err != nil {
scope . err ( err , res . ResponseWriter , req . Request )
return
}
trace . Step ( "Self-link added" )
responsewriters . WriteObject ( http . StatusCreated , scope . Kind . GroupVersion ( ) , scope . Serializer , result , w , req . Request )
}
}
// CreateNamedResource returns a function that will handle a resource creation with name.
func CreateNamedResource ( r rest . NamedCreater , scope RequestScope , typer runtime . ObjectTyper , admit admission . Interface ) restful . RouteFunction {
return createHandler ( r , scope , typer , admit , true )
}
// CreateResource returns a function that will handle a resource creation.
func CreateResource ( r rest . Creater , scope RequestScope , typer runtime . ObjectTyper , admit admission . Interface ) restful . RouteFunction {
return createHandler ( & namedCreaterAdapter { r } , scope , typer , admit , false )
}
type namedCreaterAdapter struct {
rest . Creater
}
func ( c * namedCreaterAdapter ) Create ( ctx request . Context , name string , obj runtime . Object ) ( runtime . Object , error ) {
return c . Creater . Create ( ctx , obj )
}
// PatchResource returns a function that will handle a resource patch
// TODO: Eventually PatchResource should just use GuaranteedUpdate and this routine should be a bit cleaner
func PatchResource ( r rest . Patcher , scope RequestScope , admit admission . Interface , converter runtime . ObjectConvertor ) restful . RouteFunction {
return func ( req * restful . Request , res * restful . Response ) {
w := res . ResponseWriter
// TODO: we either want to remove timeout or document it (if we
// document, move timeout out of this function and declare it in
// api_installer)
timeout := parseTimeout ( req . Request . URL . Query ( ) . Get ( "timeout" ) )
namespace , name , err := scope . Namer . Name ( req )
if err != nil {
scope . err ( err , res . ResponseWriter , req . Request )
return
}
ctx := scope . ContextFunc ( req )
ctx = request . WithNamespace ( ctx , namespace )
versionedObj , err := converter . ConvertToVersion ( r . New ( ) , scope . Kind . GroupVersion ( ) )
if err != nil {
scope . err ( err , res . ResponseWriter , req . Request )
return
}
// TODO: handle this in negotiation
contentType := req . HeaderParameter ( "Content-Type" )
// Remove "; charset=" if included in header.
if idx := strings . Index ( contentType , ";" ) ; idx > 0 {
contentType = contentType [ : idx ]
}
2017-02-03 13:41:32 +00:00
patchType := types . PatchType ( contentType )
2017-02-01 00:45:59 +00:00
patchJS , err := readBody ( req . Request )
if err != nil {
scope . err ( err , res . ResponseWriter , req . Request )
return
}
s , ok := runtime . SerializerInfoForMediaType ( scope . Serializer . SupportedMediaTypes ( ) , runtime . ContentTypeJSON )
if ! ok {
scope . err ( fmt . Errorf ( "no serializer defined for JSON" ) , res . ResponseWriter , req . Request )
return
}
gv := scope . Kind . GroupVersion ( )
codec := runtime . NewCodec (
scope . Serializer . EncoderForVersion ( s . Serializer , gv ) ,
scope . Serializer . DecoderToVersion ( s . Serializer , schema . GroupVersion { Group : gv . Group , Version : runtime . APIVersionInternal } ) ,
)
updateAdmit := func ( updatedObject runtime . Object , currentObject runtime . Object ) error {
if admit != nil && admit . Handles ( admission . Update ) {
userInfo , _ := request . UserFrom ( ctx )
return admit . Admit ( admission . NewAttributesRecord ( updatedObject , currentObject , scope . Kind , namespace , name , scope . Resource , scope . Subresource , admission . Update , userInfo ) )
}
return nil
}
result , err := patchResource ( ctx , updateAdmit , timeout , versionedObj , r , name , patchType , patchJS , scope . Namer , scope . Copier , scope . Resource , codec )
if err != nil {
scope . err ( err , res . ResponseWriter , req . Request )
return
}
if err := setSelfLink ( result , req , scope . Namer ) ; err != nil {
scope . err ( err , res . ResponseWriter , req . Request )
return
}
responsewriters . WriteObject ( http . StatusOK , scope . Kind . GroupVersion ( ) , scope . Serializer , result , w , req . Request )
}
}
type updateAdmissionFunc func ( updatedObject runtime . Object , currentObject runtime . Object ) error
// patchResource divides PatchResource for easier unit testing
func patchResource (
ctx request . Context ,
admit updateAdmissionFunc ,
timeout time . Duration ,
versionedObj runtime . Object ,
patcher rest . Patcher ,
name string ,
2017-02-03 13:41:32 +00:00
patchType types . PatchType ,
2017-02-01 00:45:59 +00:00
patchJS [ ] byte ,
namer ScopeNamer ,
copier runtime . ObjectCopier ,
resource schema . GroupVersionResource ,
codec runtime . Codec ,
) ( runtime . Object , error ) {
namespace := request . NamespaceValue ( ctx )
var (
originalObjJS [ ] byte
originalPatchedObjJS [ ] byte
2017-02-03 13:41:32 +00:00
originalObjMap map [ string ] interface { }
originalPatchMap map [ string ] interface { }
2017-02-01 00:45:59 +00:00
lastConflictErr error
)
// applyPatch is called every time GuaranteedUpdate asks for the updated object,
// and is given the currently persisted object as input.
applyPatch := func ( _ request . Context , _ , currentObject runtime . Object ) ( runtime . Object , error ) {
// Make sure we actually have a persisted currentObject
if hasUID , err := hasUID ( currentObject ) ; err != nil {
return nil , err
} else if ! hasUID {
return nil , errors . NewNotFound ( resource . GroupResource ( ) , name )
}
switch {
2017-02-03 13:41:32 +00:00
case originalObjJS == nil && originalObjMap == nil :
2017-02-01 00:45:59 +00:00
// first time through,
// 1. apply the patch
2017-02-03 13:41:32 +00:00
// 2. save the original and patched to detect whether there were conflicting changes on retries
2017-02-01 00:45:59 +00:00
objToUpdate := patcher . New ( )
2017-02-03 13:41:32 +00:00
// For performance reasons, in case of strategicpatch, we avoid json
// marshaling and unmarshaling and operate just on map[string]interface{}.
// In case of other patch types, we still have to operate on JSON
// representations.
switch patchType {
case types . JSONPatchType , types . MergePatchType :
originalJS , patchedJS , err := patchObjectJSON ( patchType , codec , currentObject , patchJS , objToUpdate , versionedObj )
if err != nil {
return nil , err
}
originalObjJS , originalPatchedObjJS = originalJS , patchedJS
case types . StrategicMergePatchType :
originalMap , patchMap , err := strategicPatchObject ( codec , currentObject , patchJS , objToUpdate , versionedObj )
if err != nil {
return nil , err
}
originalObjMap , originalPatchMap = originalMap , patchMap
2017-02-01 00:45:59 +00:00
}
if err := checkName ( objToUpdate , name , namespace , namer ) ; err != nil {
return nil , err
}
return objToUpdate , nil
default :
// on a conflict,
// 1. build a strategic merge patch from originalJS and the patchedJS. Different patch types can
// be specified, but a strategic merge patch should be expressive enough handle them. Build the
// patch with this type to handle those cases.
// 2. build a strategic merge patch from originalJS and the currentJS
// 3. ensure no conflicts between the two patches
// 4. apply the #1 patch to the currentJS object
2017-02-03 13:41:32 +00:00
// TODO: This should be one-step conversion that doesn't require
// json marshaling and unmarshaling once #39017 is fixed.
data , err := runtime . Encode ( codec , currentObject )
2017-02-01 00:45:59 +00:00
if err != nil {
return nil , err
}
2017-02-03 13:41:32 +00:00
currentObjMap := make ( map [ string ] interface { } )
if err := json . Unmarshal ( data , & currentObjMap ) ; err != nil {
2017-02-01 00:45:59 +00:00
return nil , err
}
2017-02-03 13:41:32 +00:00
var currentPatchMap map [ string ] interface { }
if originalObjMap != nil {
var err error
currentPatchMap , err = strategicpatch . CreateTwoWayMergeMapPatch ( originalObjMap , currentObjMap , versionedObj )
if err != nil {
return nil , err
}
} else {
if originalPatchMap == nil {
// Compute original patch, if we already didn't do this in previous retries.
originalPatch , err := strategicpatch . CreateTwoWayMergePatch ( originalObjJS , originalPatchedObjJS , versionedObj )
if err != nil {
return nil , err
}
originalPatchMap = make ( map [ string ] interface { } )
if err := json . Unmarshal ( originalPatch , & originalPatchMap ) ; err != nil {
return nil , err
}
}
// Compute current patch.
currentObjJS , err := runtime . Encode ( codec , currentObject )
if err != nil {
return nil , err
}
currentPatch , err := strategicpatch . CreateTwoWayMergePatch ( originalObjJS , currentObjJS , versionedObj )
if err != nil {
return nil , err
}
currentPatchMap = make ( map [ string ] interface { } )
if err := json . Unmarshal ( currentPatch , & currentPatchMap ) ; err != nil {
return nil , err
}
2017-02-01 00:45:59 +00:00
}
2017-02-03 13:41:32 +00:00
hasConflicts , err := strategicpatch . HasConflicts ( originalPatchMap , currentPatchMap )
2017-02-01 00:45:59 +00:00
if err != nil {
return nil , err
}
if hasConflicts {
2017-02-03 13:41:32 +00:00
if glog . V ( 4 ) {
diff1 , _ := json . Marshal ( currentPatchMap )
diff2 , _ := json . Marshal ( originalPatchMap )
glog . Infof ( "patchResource failed for resource %s, because there is a meaningful conflict.\n diff1=%v\n, diff2=%v\n" , name , diff1 , diff2 )
}
2017-02-01 00:45:59 +00:00
// Return the last conflict error we got if we have one
if lastConflictErr != nil {
return nil , lastConflictErr
}
// Otherwise manufacture one of our own
return nil , errors . NewConflict ( resource . GroupResource ( ) , name , nil )
}
objToUpdate := patcher . New ( )
2017-02-03 13:41:32 +00:00
if err := applyPatchToObject ( codec , currentObjMap , originalPatchMap , objToUpdate , versionedObj ) ; err != nil {
2017-02-01 00:45:59 +00:00
return nil , err
}
2017-02-03 13:41:32 +00:00
2017-02-01 00:45:59 +00:00
return objToUpdate , nil
}
}
// applyAdmission is called every time GuaranteedUpdate asks for the updated object,
// and is given the currently persisted object and the patched object as input.
applyAdmission := func ( ctx request . Context , patchedObject runtime . Object , currentObject runtime . Object ) ( runtime . Object , error ) {
return patchedObject , admit ( patchedObject , currentObject )
}
updatedObjectInfo := rest . DefaultUpdatedObjectInfo ( nil , copier , applyPatch , applyAdmission )
return finishRequest ( timeout , func ( ) ( runtime . Object , error ) {
updateObject , _ , updateErr := patcher . Update ( ctx , name , updatedObjectInfo )
for i := 0 ; i < maxRetryWhenPatchConflicts && ( errors . IsConflict ( updateErr ) ) ; i ++ {
lastConflictErr = updateErr
updateObject , _ , updateErr = patcher . Update ( ctx , name , updatedObjectInfo )
}
return updateObject , updateErr
} )
}
// UpdateResource returns a function that will handle a resource update
func UpdateResource ( r rest . Updater , scope RequestScope , typer runtime . ObjectTyper , admit admission . Interface ) restful . RouteFunction {
return func ( req * restful . Request , res * restful . Response ) {
// For performance tracking purposes.
trace := util . NewTrace ( "Update " + req . Request . URL . Path )
defer trace . LogIfLong ( 500 * time . Millisecond )
w := res . ResponseWriter
// TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer)
timeout := parseTimeout ( req . Request . URL . Query ( ) . Get ( "timeout" ) )
namespace , name , err := scope . Namer . Name ( req )
if err != nil {
scope . err ( err , res . ResponseWriter , req . Request )
return
}
ctx := scope . ContextFunc ( req )
ctx = request . WithNamespace ( ctx , namespace )
body , err := readBody ( req . Request )
if err != nil {
scope . err ( err , res . ResponseWriter , req . Request )
return
}
s , err := negotiation . NegotiateInputSerializer ( req . Request , scope . Serializer )
if err != nil {
scope . err ( err , res . ResponseWriter , req . Request )
return
}
defaultGVK := scope . Kind
original := r . New ( )
trace . Step ( "About to convert to expected version" )
obj , gvk , err := scope . Serializer . DecoderToVersion ( s . Serializer , defaultGVK . GroupVersion ( ) ) . Decode ( body , & defaultGVK , original )
if err != nil {
err = transformDecodeError ( typer , err , original , gvk , body )
scope . err ( err , res . ResponseWriter , req . Request )
return
}
if gvk . GroupVersion ( ) != defaultGVK . GroupVersion ( ) {
err = errors . NewBadRequest ( fmt . Sprintf ( "the API version in the data (%s) does not match the expected API version (%s)" , gvk . GroupVersion ( ) , defaultGVK . GroupVersion ( ) ) )
scope . err ( err , res . ResponseWriter , req . Request )
return
}
trace . Step ( "Conversion done" )
if err := checkName ( obj , name , namespace , scope . Namer ) ; err != nil {
scope . err ( err , res . ResponseWriter , req . Request )
return
}
var transformers [ ] rest . TransformFunc
if admit != nil && admit . Handles ( admission . Update ) {
transformers = append ( transformers , func ( ctx request . Context , newObj , oldObj runtime . Object ) ( runtime . Object , error ) {
userInfo , _ := request . UserFrom ( ctx )
return newObj , admit . Admit ( admission . NewAttributesRecord ( newObj , oldObj , scope . Kind , namespace , name , scope . Resource , scope . Subresource , admission . Update , userInfo ) )
} )
}
trace . Step ( "About to store object in database" )
wasCreated := false
result , err := finishRequest ( timeout , func ( ) ( runtime . Object , error ) {
obj , created , err := r . Update ( ctx , name , rest . DefaultUpdatedObjectInfo ( obj , scope . Copier , transformers ... ) )
wasCreated = created
return obj , err
} )
if err != nil {
scope . err ( err , res . ResponseWriter , req . Request )
return
}
trace . Step ( "Object stored in database" )
if err := setSelfLink ( result , req , scope . Namer ) ; err != nil {
scope . err ( err , res . ResponseWriter , req . Request )
return
}
trace . Step ( "Self-link added" )
status := http . StatusOK
if wasCreated {
status = http . StatusCreated
}
responsewriters . WriteObject ( status , scope . Kind . GroupVersion ( ) , scope . Serializer , result , w , req . Request )
}
}
// DeleteResource returns a function that will handle a resource deletion
func DeleteResource ( r rest . GracefulDeleter , allowsOptions bool , scope RequestScope , admit admission . Interface ) restful . RouteFunction {
return func ( req * restful . Request , res * restful . Response ) {
// For performance tracking purposes.
trace := util . NewTrace ( "Delete " + req . Request . URL . Path )
defer trace . LogIfLong ( 500 * time . Millisecond )
w := res . ResponseWriter
// TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer)
timeout := parseTimeout ( req . Request . URL . Query ( ) . Get ( "timeout" ) )
namespace , name , err := scope . Namer . Name ( req )
if err != nil {
scope . err ( err , res . ResponseWriter , req . Request )
return
}
ctx := scope . ContextFunc ( req )
ctx = request . WithNamespace ( ctx , namespace )
2017-02-03 13:41:32 +00:00
options := & metav1 . DeleteOptions { }
2017-02-01 00:45:59 +00:00
if allowsOptions {
body , err := readBody ( req . Request )
if err != nil {
scope . err ( err , res . ResponseWriter , req . Request )
return
}
if len ( body ) > 0 {
s , err := negotiation . NegotiateInputSerializer ( req . Request , scope . Serializer )
if err != nil {
scope . err ( err , res . ResponseWriter , req . Request )
return
}
defaultGVK := scope . Kind . GroupVersion ( ) . WithKind ( "DeleteOptions" )
obj , _ , err := scope . Serializer . DecoderToVersion ( s . Serializer , defaultGVK . GroupVersion ( ) ) . Decode ( body , & defaultGVK , options )
if err != nil {
scope . err ( err , res . ResponseWriter , req . Request )
return
}
if obj != options {
scope . err ( fmt . Errorf ( "decoded object cannot be converted to DeleteOptions" ) , res . ResponseWriter , req . Request )
return
}
} else {
if values := req . Request . URL . Query ( ) ; len ( values ) > 0 {
if err := scope . ParameterCodec . DecodeParameters ( values , scope . Kind . GroupVersion ( ) , options ) ; err != nil {
scope . err ( err , res . ResponseWriter , req . Request )
return
}
}
}
}
if admit != nil && admit . Handles ( admission . Delete ) {
userInfo , _ := request . UserFrom ( ctx )
err = admit . Admit ( admission . NewAttributesRecord ( nil , nil , scope . Kind , namespace , name , scope . Resource , scope . Subresource , admission . Delete , userInfo ) )
if err != nil {
scope . err ( err , res . ResponseWriter , req . Request )
return
}
}
trace . Step ( "About do delete object from database" )
result , err := finishRequest ( timeout , func ( ) ( runtime . Object , error ) {
return r . Delete ( ctx , name , options )
} )
if err != nil {
scope . err ( err , res . ResponseWriter , req . Request )
return
}
trace . Step ( "Object deleted from database" )
// if the rest.Deleter returns a nil object, fill out a status. Callers may return a valid
// object with the response.
if result == nil {
result = & metav1 . Status {
Status : metav1 . StatusSuccess ,
Code : http . StatusOK ,
Details : & metav1 . StatusDetails {
Name : name ,
Kind : scope . Kind . Kind ,
} ,
}
} else {
// when a non-status response is returned, set the self link
if _ , ok := result . ( * metav1 . Status ) ; ! ok {
if err := setSelfLink ( result , req , scope . Namer ) ; err != nil {
scope . err ( err , res . ResponseWriter , req . Request )
return
}
}
}
responsewriters . WriteObject ( http . StatusOK , scope . Kind . GroupVersion ( ) , scope . Serializer , result , w , req . Request )
}
}
// DeleteCollection returns a function that will handle a collection deletion
func DeleteCollection ( r rest . CollectionDeleter , checkBody bool , scope RequestScope , admit admission . Interface ) restful . RouteFunction {
return func ( req * restful . Request , res * restful . Response ) {
w := res . ResponseWriter
// TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer)
timeout := parseTimeout ( req . Request . URL . Query ( ) . Get ( "timeout" ) )
namespace , err := scope . Namer . Namespace ( req )
if err != nil {
scope . err ( err , res . ResponseWriter , req . Request )
return
}
ctx := scope . ContextFunc ( req )
ctx = request . WithNamespace ( ctx , namespace )
if admit != nil && admit . Handles ( admission . Delete ) {
userInfo , _ := request . UserFrom ( ctx )
err = admit . Admit ( admission . NewAttributesRecord ( nil , nil , scope . Kind , namespace , "" , scope . Resource , scope . Subresource , admission . Delete , userInfo ) )
if err != nil {
scope . err ( err , res . ResponseWriter , req . Request )
return
}
}
2017-02-03 13:41:32 +00:00
listOptions := metainternalversion . ListOptions { }
if err := metainternalversion . ParameterCodec . DecodeParameters ( req . Request . URL . Query ( ) , scope . MetaGroupVersion , & listOptions ) ; err != nil {
2017-02-01 00:45:59 +00:00
scope . err ( err , res . ResponseWriter , req . Request )
return
}
// transform fields
// TODO: DecodeParametersInto should do this.
if listOptions . FieldSelector != nil {
fn := func ( label , value string ) ( newLabel , newValue string , err error ) {
return scope . Convertor . ConvertFieldLabel ( scope . Kind . GroupVersion ( ) . String ( ) , scope . Kind . Kind , label , value )
}
if listOptions . FieldSelector , err = listOptions . FieldSelector . Transform ( fn ) ; err != nil {
// TODO: allow bad request to set field causes based on query parameters
err = errors . NewBadRequest ( err . Error ( ) )
scope . err ( err , res . ResponseWriter , req . Request )
return
}
}
2017-02-03 13:41:32 +00:00
options := & metav1 . DeleteOptions { }
2017-02-01 00:45:59 +00:00
if checkBody {
body , err := readBody ( req . Request )
if err != nil {
scope . err ( err , res . ResponseWriter , req . Request )
return
}
if len ( body ) > 0 {
s , err := negotiation . NegotiateInputSerializer ( req . Request , scope . Serializer )
if err != nil {
scope . err ( err , res . ResponseWriter , req . Request )
return
}
defaultGVK := scope . Kind . GroupVersion ( ) . WithKind ( "DeleteOptions" )
obj , _ , err := scope . Serializer . DecoderToVersion ( s . Serializer , defaultGVK . GroupVersion ( ) ) . Decode ( body , & defaultGVK , options )
if err != nil {
scope . err ( err , res . ResponseWriter , req . Request )
return
}
if obj != options {
scope . err ( fmt . Errorf ( "decoded object cannot be converted to DeleteOptions" ) , res . ResponseWriter , req . Request )
return
}
}
}
result , err := finishRequest ( timeout , func ( ) ( runtime . Object , error ) {
return r . DeleteCollection ( ctx , options , & listOptions )
} )
if err != nil {
scope . err ( err , res . ResponseWriter , req . Request )
return
}
// if the rest.Deleter returns a nil object, fill out a status. Callers may return a valid
// object with the response.
if result == nil {
result = & metav1 . Status {
Status : metav1 . StatusSuccess ,
Code : http . StatusOK ,
Details : & metav1 . StatusDetails {
Kind : scope . Kind . Kind ,
} ,
}
} else {
// when a non-status response is returned, set the self link
if _ , ok := result . ( * metav1 . Status ) ; ! ok {
if _ , err := setListSelfLink ( result , req , scope . Namer ) ; err != nil {
scope . err ( err , res . ResponseWriter , req . Request )
return
}
}
}
responsewriters . WriteObjectNegotiated ( scope . Serializer , scope . Kind . GroupVersion ( ) , w , req . Request , http . StatusOK , result )
}
}
// resultFunc is a function that returns a rest result and can be run in a goroutine
type resultFunc func ( ) ( runtime . Object , error )
// finishRequest makes a given resultFunc asynchronous and handles errors returned by the response.
// Any api.Status object returned is considered an "error", which interrupts the normal response flow.
func finishRequest ( timeout time . Duration , fn resultFunc ) ( result runtime . Object , err error ) {
// these channels need to be buffered to prevent the goroutine below from hanging indefinitely
// when the select statement reads something other than the one the goroutine sends on.
ch := make ( chan runtime . Object , 1 )
errCh := make ( chan error , 1 )
panicCh := make ( chan interface { } , 1 )
go func ( ) {
// panics don't cross goroutine boundaries, so we have to handle ourselves
defer utilruntime . HandleCrash ( func ( panicReason interface { } ) {
// Propagate to parent goroutine
panicCh <- panicReason
} )
if result , err := fn ( ) ; err != nil {
errCh <- err
} else {
ch <- result
}
} ( )
select {
case result = <- ch :
if status , ok := result . ( * metav1 . Status ) ; ok {
return nil , errors . FromObject ( status )
}
return result , nil
case err = <- errCh :
return nil , err
case p := <- panicCh :
panic ( p )
case <- time . After ( timeout ) :
return nil , errors . NewTimeoutError ( "request did not complete within allowed duration" , 0 )
}
}
// transformDecodeError adds additional information when a decode fails.
func transformDecodeError ( typer runtime . ObjectTyper , baseErr error , into runtime . Object , gvk * schema . GroupVersionKind , body [ ] byte ) error {
objGVKs , _ , err := typer . ObjectKinds ( into )
if err != nil {
return err
}
objGVK := objGVKs [ 0 ]
if gvk != nil && len ( gvk . Kind ) > 0 {
return errors . NewBadRequest ( fmt . Sprintf ( "%s in version %q cannot be handled as a %s: %v" , gvk . Kind , gvk . Version , objGVK . Kind , baseErr ) )
}
summary := summarizeData ( body , 30 )
return errors . NewBadRequest ( fmt . Sprintf ( "the object provided is unrecognized (must be of type %s): %v (%s)" , objGVK . Kind , baseErr , summary ) )
}
// setSelfLink sets the self link of an object (or the child items in a list) to the base URL of the request
// plus the path and query generated by the provided linkFunc
func setSelfLink ( obj runtime . Object , req * restful . Request , namer ScopeNamer ) error {
// TODO: SelfLink generation should return a full URL?
uri , err := namer . GenerateLink ( req , obj )
if err != nil {
return nil
}
return namer . SetSelfLink ( obj , uri )
}
func hasUID ( obj runtime . Object ) ( bool , error ) {
if obj == nil {
return false , nil
}
accessor , err := meta . Accessor ( obj )
if err != nil {
return false , errors . NewInternalError ( err )
}
if len ( accessor . GetUID ( ) ) == 0 {
return false , nil
}
return true , nil
}
// checkName checks the provided name against the request
func checkName ( obj runtime . Object , name , namespace string , namer ScopeNamer ) error {
if objNamespace , objName , err := namer . ObjectName ( obj ) ; err == nil {
if err != nil {
return err
}
if objName != name {
return errors . NewBadRequest ( fmt . Sprintf (
"the name of the object (%s) does not match the name on the URL (%s)" , objName , name ) )
}
if len ( namespace ) > 0 {
if len ( objNamespace ) > 0 && objNamespace != namespace {
return errors . NewBadRequest ( fmt . Sprintf (
"the namespace of the object (%s) does not match the namespace on the request (%s)" , objNamespace , namespace ) )
}
}
}
return nil
}
// setListSelfLink sets the self link of a list to the base URL, then sets the self links
// on all child objects returned. Returns the number of items in the list.
func setListSelfLink ( obj runtime . Object , req * restful . Request , namer ScopeNamer ) ( int , error ) {
if ! meta . IsListType ( obj ) {
return 0 , nil
}
uri , err := namer . GenerateListLink ( req )
if err != nil {
return 0 , err
}
if err := namer . SetSelfLink ( obj , uri ) ; err != nil {
glog . V ( 4 ) . Infof ( "Unable to set self link on object: %v" , err )
}
count := 0
err = meta . EachListItem ( obj , func ( obj runtime . Object ) error {
count ++
return setSelfLink ( obj , req , namer )
} )
return count , err
}
func summarizeData ( data [ ] byte , maxLength int ) string {
switch {
case len ( data ) == 0 :
return "<empty>"
case data [ 0 ] == '{' :
if len ( data ) > maxLength {
return string ( data [ : maxLength ] ) + " ..."
}
return string ( data )
default :
if len ( data ) > maxLength {
return hex . EncodeToString ( data [ : maxLength ] ) + " ..."
}
return hex . EncodeToString ( data )
}
}
func readBody ( req * http . Request ) ( [ ] byte , error ) {
defer req . Body . Close ( )
return ioutil . ReadAll ( req . Body )
}
func parseTimeout ( str string ) time . Duration {
if str != "" {
timeout , err := time . ParseDuration ( str )
if err == nil {
return timeout
}
glog . Errorf ( "Failed to parse %q: %v" , str , err )
}
return 30 * time . Second
}