2015-04-06 23:27:53 +00:00
|
|
|
/*
|
2015-05-01 16:19:44 +00:00
|
|
|
Copyright 2015 The Kubernetes Authors All rights reserved.
|
2015-04-06 23:27:53 +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 testclient
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"reflect"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
|
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/yaml"
|
|
|
|
)
|
|
|
|
|
|
|
|
// ObjectRetriever abstracts the implementation for retrieving or setting generic
|
|
|
|
// objects. It is intended to be used to fake calls to a server by returning
|
|
|
|
// objects based on their kind and name.
|
|
|
|
type ObjectRetriever interface {
|
|
|
|
// Kind should return a resource or a list of resources (depending on the provided kind and
|
|
|
|
// name). It should return an error if the caller should communicate an error to the server.
|
|
|
|
Kind(kind, name string) (runtime.Object, error)
|
|
|
|
// Add adds a runtime object for test purposes into this object.
|
|
|
|
Add(runtime.Object) error
|
|
|
|
}
|
|
|
|
|
|
|
|
// ObjectReaction returns a ReactionFunc that takes a generic action string of the form
|
|
|
|
// <verb>-<resource> or <verb>-<subresource>-<resource> and attempts to return a runtime
|
|
|
|
// Object or error that matches the requested action. For instance, list-replicationControllers
|
|
|
|
// should attempt to return a list of replication controllers. This method delegates to the
|
|
|
|
// ObjectRetriever interface to satisfy retrieval of lists or retrieval of single items.
|
|
|
|
// TODO: add support for sub resources
|
|
|
|
func ObjectReaction(o ObjectRetriever, mapper meta.RESTMapper) ReactionFunc {
|
|
|
|
return func(action FakeAction) (runtime.Object, error) {
|
|
|
|
segments := strings.Split(action.Action, "-")
|
|
|
|
var verb, resource string
|
|
|
|
switch len(segments) {
|
|
|
|
case 3:
|
|
|
|
verb, _, resource = segments[0], segments[1], segments[2]
|
|
|
|
case 2:
|
|
|
|
verb, resource = segments[0], segments[1]
|
|
|
|
default:
|
|
|
|
return nil, fmt.Errorf("unrecognized action, need two or three segments <verb>-<resource> or <verb>-<subresource>-<resource>: %s", action.Action)
|
|
|
|
}
|
|
|
|
_, kind, err := mapper.VersionAndKindForResource(resource)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("unrecognized action %s: %v", resource, err)
|
|
|
|
}
|
|
|
|
// TODO: have mapper return a Kind for a subresource?
|
|
|
|
switch verb {
|
|
|
|
case "list", "search":
|
|
|
|
return o.Kind(kind+"List", "")
|
|
|
|
case "get", "create", "update", "delete":
|
|
|
|
// TODO: handle sub resources
|
|
|
|
if s, ok := action.Value.(string); ok && action.Value != nil {
|
|
|
|
return o.Kind(kind, s)
|
|
|
|
}
|
|
|
|
return o.Kind(kind, "unknown")
|
|
|
|
default:
|
|
|
|
return nil, fmt.Errorf("no reaction implemented for %s", action.Action)
|
|
|
|
}
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// AddObjectsFromPath loads the JSON or YAML file containing Kubernetes API resources
|
|
|
|
// and adds them to the provided ObjectRetriever.
|
2015-04-29 03:15:16 +00:00
|
|
|
func AddObjectsFromPath(path string, o ObjectRetriever, decoder runtime.Decoder) error {
|
2015-04-06 23:27:53 +00:00
|
|
|
data, err := ioutil.ReadFile(path)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
data, err = yaml.ToJSON(data)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2015-04-29 03:15:16 +00:00
|
|
|
obj, err := decoder.Decode(data)
|
2015-04-06 23:27:53 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := o.Add(obj); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type objects struct {
|
|
|
|
types map[string][]runtime.Object
|
|
|
|
last map[string]int
|
2015-04-29 03:15:16 +00:00
|
|
|
scheme runtime.ObjectScheme
|
|
|
|
decoder runtime.ObjectDecoder
|
2015-04-06 23:27:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var _ ObjectRetriever = &objects{}
|
|
|
|
|
|
|
|
// NewObjects implements the ObjectRetriever interface by introspecting the
|
|
|
|
// objects provided to Add() and returning them when the Kind method is invoked.
|
|
|
|
// If an api.List object is provided to Add(), each child item is added. If an
|
|
|
|
// object is added that is itself a list (PodList, ServiceList) then that is added
|
|
|
|
// to the "PodList" kind. If no PodList is added, the retriever will take any loaded
|
|
|
|
// Pods and return them in a list. If an api.Status is added, and the Details.Kind field
|
|
|
|
// is set, that status will be returned instead (as an error if Status != Success, or
|
|
|
|
// as a runtime.Object if Status == Success). If multiple PodLists are provided, they
|
|
|
|
// will be returned in order by the Kind call, and the last PodList will be reused for
|
|
|
|
// subsequent calls.
|
2015-04-29 03:15:16 +00:00
|
|
|
func NewObjects(scheme runtime.ObjectScheme, decoder runtime.ObjectDecoder) ObjectRetriever {
|
2015-04-06 23:27:53 +00:00
|
|
|
return objects{
|
|
|
|
types: make(map[string][]runtime.Object),
|
|
|
|
last: make(map[string]int),
|
2015-04-29 03:15:16 +00:00
|
|
|
scheme: scheme,
|
|
|
|
decoder: decoder,
|
2015-04-06 23:27:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o objects) Kind(kind, name string) (runtime.Object, error) {
|
2015-04-29 03:15:16 +00:00
|
|
|
empty, _ := o.scheme.New("", kind)
|
2015-04-06 23:27:53 +00:00
|
|
|
nilValue := reflect.Zero(reflect.TypeOf(empty)).Interface().(runtime.Object)
|
|
|
|
|
|
|
|
arr, ok := o.types[kind]
|
|
|
|
if !ok {
|
|
|
|
if strings.HasSuffix(kind, "List") {
|
|
|
|
itemKind := kind[:len(kind)-4]
|
|
|
|
arr, ok := o.types[itemKind]
|
|
|
|
if !ok {
|
|
|
|
return empty, nil
|
|
|
|
}
|
2015-04-29 03:15:16 +00:00
|
|
|
out, err := o.scheme.New("", kind)
|
2015-04-06 23:27:53 +00:00
|
|
|
if err != nil {
|
|
|
|
return nilValue, err
|
|
|
|
}
|
|
|
|
if err := runtime.SetList(out, arr); err != nil {
|
|
|
|
return nilValue, err
|
|
|
|
}
|
2015-04-29 03:15:16 +00:00
|
|
|
if out, err = o.scheme.Copy(out); err != nil {
|
2015-04-06 23:27:53 +00:00
|
|
|
return nilValue, err
|
|
|
|
}
|
|
|
|
return out, nil
|
|
|
|
}
|
|
|
|
return nilValue, errors.NewNotFound(kind, name)
|
|
|
|
}
|
|
|
|
|
|
|
|
index := o.last[kind]
|
|
|
|
if index >= len(arr) {
|
|
|
|
index = len(arr) - 1
|
|
|
|
}
|
|
|
|
if index < 0 {
|
|
|
|
return nilValue, errors.NewNotFound(kind, name)
|
|
|
|
}
|
2015-04-29 03:15:16 +00:00
|
|
|
out, err := o.scheme.Copy(arr[index])
|
2015-04-06 23:27:53 +00:00
|
|
|
if err != nil {
|
|
|
|
return nilValue, err
|
|
|
|
}
|
|
|
|
o.last[kind] = index + 1
|
|
|
|
|
|
|
|
if status, ok := out.(*api.Status); ok {
|
|
|
|
if status.Details != nil {
|
|
|
|
status.Details.Kind = kind
|
|
|
|
}
|
|
|
|
if status.Status != api.StatusSuccess {
|
|
|
|
return nilValue, &errors.StatusError{*status}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return out, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o objects) Add(obj runtime.Object) error {
|
2015-04-29 03:15:16 +00:00
|
|
|
_, kind, err := o.scheme.ObjectVersionAndKind(obj)
|
2015-04-06 23:27:53 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case runtime.IsListType(obj):
|
|
|
|
if kind != "List" {
|
|
|
|
o.types[kind] = append(o.types[kind], obj)
|
|
|
|
}
|
|
|
|
|
|
|
|
list, err := runtime.ExtractList(obj)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2015-04-29 03:15:16 +00:00
|
|
|
if errs := runtime.DecodeList(list, o.decoder); len(errs) > 0 {
|
|
|
|
return errs[0]
|
|
|
|
}
|
2015-04-06 23:27:53 +00:00
|
|
|
for _, obj := range list {
|
|
|
|
if err := o.Add(obj); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
if status, ok := obj.(*api.Status); ok && status.Details != nil {
|
|
|
|
kind = status.Details.Kind
|
|
|
|
}
|
|
|
|
o.types[kind] = append(o.types[kind], obj)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|