576 lines
19 KiB
Go
576 lines
19 KiB
Go
/*
|
|
Copyright 2018 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 customresource_test
|
|
|
|
import (
|
|
"io"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
autoscalingv1 "k8s.io/api/autoscaling/v1"
|
|
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
|
"k8s.io/apimachinery/pkg/api/errors"
|
|
metainternal "k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
"k8s.io/apimachinery/pkg/util/diff"
|
|
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
|
"k8s.io/apiserver/pkg/registry/generic"
|
|
registrytest "k8s.io/apiserver/pkg/registry/generic/testing"
|
|
"k8s.io/apiserver/pkg/registry/rest"
|
|
etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing"
|
|
|
|
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
|
"k8s.io/apiextensions-apiserver/pkg/apiserver"
|
|
"k8s.io/apiextensions-apiserver/pkg/crdserverscheme"
|
|
"k8s.io/apiextensions-apiserver/pkg/registry/customresource"
|
|
"k8s.io/apiextensions-apiserver/pkg/registry/customresource/tableconvertor"
|
|
)
|
|
|
|
func newStorage(t *testing.T) (customresource.CustomResourceStorage, *etcdtesting.EtcdTestServer) {
|
|
server, etcdStorage := etcdtesting.NewUnsecuredEtcd3TestClientServer(t)
|
|
etcdStorage.Codec = unstructuredJsonCodec{}
|
|
restOptions := generic.RESTOptions{StorageConfig: etcdStorage, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: 1, ResourcePrefix: "noxus"}
|
|
|
|
parameterScheme := runtime.NewScheme()
|
|
parameterScheme.AddUnversionedTypes(schema.GroupVersion{Group: "mygroup.example.com", Version: "v1beta1"},
|
|
&metav1.ListOptions{},
|
|
&metav1.ExportOptions{},
|
|
&metav1.GetOptions{},
|
|
&metav1.DeleteOptions{},
|
|
)
|
|
|
|
typer := apiserver.UnstructuredObjectTyper{
|
|
Delegate: parameterScheme,
|
|
UnstructuredTyper: crdserverscheme.NewUnstructuredObjectTyper(),
|
|
}
|
|
|
|
kind := schema.GroupVersionKind{Group: "mygroup.example.com", Version: "v1beta1", Kind: "Noxu"}
|
|
|
|
labelSelectorPath := ".status.labelSelector"
|
|
scale := &apiextensions.CustomResourceSubresourceScale{
|
|
SpecReplicasPath: ".spec.replicas",
|
|
StatusReplicasPath: ".status.replicas",
|
|
LabelSelectorPath: &labelSelectorPath,
|
|
}
|
|
|
|
status := &apiextensions.CustomResourceSubresourceStatus{}
|
|
|
|
headers := []apiextensions.CustomResourceColumnDefinition{
|
|
{Name: "Age", Type: "date", JSONPath: ".metadata.creationTimestamp"},
|
|
{Name: "Replicas", Type: "integer", JSONPath: ".spec.replicas"},
|
|
{Name: "Missing", Type: "string", JSONPath: ".spec.missing"},
|
|
{Name: "Invalid", Type: "integer", JSONPath: ".spec.string"},
|
|
{Name: "String", Type: "string", JSONPath: ".spec.string"},
|
|
{Name: "StringFloat64", Type: "string", JSONPath: ".spec.float64"},
|
|
{Name: "StringInt64", Type: "string", JSONPath: ".spec.replicas"},
|
|
{Name: "StringBool", Type: "string", JSONPath: ".spec.bool"},
|
|
{Name: "Float64", Type: "number", JSONPath: ".spec.float64"},
|
|
{Name: "Bool", Type: "boolean", JSONPath: ".spec.bool"},
|
|
}
|
|
table, _ := tableconvertor.New(headers)
|
|
|
|
storage := customresource.NewStorage(
|
|
schema.GroupResource{Group: "mygroup.example.com", Resource: "noxus"},
|
|
schema.GroupVersionKind{Group: "mygroup.example.com", Version: "v1beta1", Kind: "NoxuItemList"},
|
|
customresource.NewStrategy(
|
|
typer,
|
|
true,
|
|
kind,
|
|
nil,
|
|
nil,
|
|
status,
|
|
scale,
|
|
),
|
|
restOptions,
|
|
[]string{"all"},
|
|
table,
|
|
)
|
|
|
|
return storage, server
|
|
}
|
|
|
|
// createCustomResource is a helper function that returns a CustomResource with the updated resource version.
|
|
func createCustomResource(storage *customresource.REST, cr unstructured.Unstructured, t *testing.T) (unstructured.Unstructured, error) {
|
|
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), cr.GetNamespace())
|
|
obj, err := storage.Create(ctx, &cr, rest.ValidateAllObjectFunc, &metav1.CreateOptions{})
|
|
if err != nil {
|
|
t.Errorf("Failed to create CustomResource, %v", err)
|
|
}
|
|
newCR := obj.(*unstructured.Unstructured)
|
|
return *newCR, nil
|
|
}
|
|
|
|
func validNewCustomResource() *unstructured.Unstructured {
|
|
return &unstructured.Unstructured{
|
|
Object: map[string]interface{}{
|
|
"apiVersion": "mygroup.example.com/v1beta1",
|
|
"kind": "Noxu",
|
|
"metadata": map[string]interface{}{
|
|
"namespace": "default",
|
|
"name": "foo",
|
|
"creationTimestamp": time.Now().Add(-time.Hour*12 - 30*time.Minute).UTC().Format(time.RFC3339),
|
|
},
|
|
"spec": map[string]interface{}{
|
|
"replicas": int64(7),
|
|
"string": "string",
|
|
"float64": float64(3.1415926),
|
|
"bool": true,
|
|
"stringList": []interface{}{"foo", "bar"},
|
|
"mixedList": []interface{}{"foo", int64(42)},
|
|
"nonPrimitiveList": []interface{}{"foo", []interface{}{int64(1), int64(2)}},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
var validCustomResource = *validNewCustomResource()
|
|
|
|
func TestCreate(t *testing.T) {
|
|
storage, server := newStorage(t)
|
|
defer server.Terminate(t)
|
|
defer storage.CustomResource.Store.DestroyFunc()
|
|
test := registrytest.New(t, storage.CustomResource.Store)
|
|
cr := validNewCustomResource()
|
|
cr.SetNamespace("")
|
|
test.TestCreate(
|
|
cr,
|
|
)
|
|
}
|
|
|
|
func TestGet(t *testing.T) {
|
|
storage, server := newStorage(t)
|
|
defer server.Terminate(t)
|
|
defer storage.CustomResource.Store.DestroyFunc()
|
|
test := registrytest.New(t, storage.CustomResource.Store)
|
|
test.TestGet(validNewCustomResource())
|
|
}
|
|
|
|
func TestList(t *testing.T) {
|
|
storage, server := newStorage(t)
|
|
defer server.Terminate(t)
|
|
defer storage.CustomResource.Store.DestroyFunc()
|
|
test := registrytest.New(t, storage.CustomResource.Store)
|
|
test.TestList(validNewCustomResource())
|
|
}
|
|
|
|
func TestDelete(t *testing.T) {
|
|
storage, server := newStorage(t)
|
|
defer server.Terminate(t)
|
|
defer storage.CustomResource.Store.DestroyFunc()
|
|
test := registrytest.New(t, storage.CustomResource.Store)
|
|
test.TestDelete(validNewCustomResource())
|
|
}
|
|
|
|
func TestGenerationNumber(t *testing.T) {
|
|
storage, server := newStorage(t)
|
|
defer server.Terminate(t)
|
|
defer storage.CustomResource.Store.DestroyFunc()
|
|
modifiedRno := *validNewCustomResource()
|
|
modifiedRno.SetGeneration(10)
|
|
ctx := genericapirequest.NewDefaultContext()
|
|
cr, err := createCustomResource(storage.CustomResource, modifiedRno, t)
|
|
if err != nil {
|
|
t.Errorf("unexpected error: %v", err)
|
|
}
|
|
etcdCR, err := storage.CustomResource.Get(ctx, cr.GetName(), &metav1.GetOptions{})
|
|
if err != nil {
|
|
t.Errorf("unexpected error: %v", err)
|
|
}
|
|
storedCR, _ := etcdCR.(*unstructured.Unstructured)
|
|
|
|
// Generation initialization
|
|
if storedCR.GetGeneration() != 1 {
|
|
t.Fatalf("Unexpected generation number %v", storedCR.GetGeneration())
|
|
}
|
|
|
|
// Updates to spec should increment the generation number
|
|
setSpecReplicas(storedCR, getSpecReplicas(storedCR)+1)
|
|
if _, _, err := storage.CustomResource.Update(ctx, storedCR.GetName(), rest.DefaultUpdatedObjectInfo(storedCR), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}); err != nil {
|
|
t.Errorf("unexpected error: %v", err)
|
|
}
|
|
etcdCR, err = storage.CustomResource.Get(ctx, cr.GetName(), &metav1.GetOptions{})
|
|
if err != nil {
|
|
t.Errorf("unexpected error: %v", err)
|
|
}
|
|
storedCR, _ = etcdCR.(*unstructured.Unstructured)
|
|
if storedCR.GetGeneration() != 2 {
|
|
t.Fatalf("Unexpected generation, spec: %v", storedCR.GetGeneration())
|
|
}
|
|
|
|
// Updates to status should not increment the generation number
|
|
setStatusReplicas(storedCR, getStatusReplicas(storedCR)+1)
|
|
if _, _, err := storage.CustomResource.Update(ctx, storedCR.GetName(), rest.DefaultUpdatedObjectInfo(storedCR), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}); err != nil {
|
|
t.Errorf("unexpected error: %v", err)
|
|
}
|
|
etcdCR, err = storage.CustomResource.Get(ctx, cr.GetName(), &metav1.GetOptions{})
|
|
if err != nil {
|
|
t.Errorf("unexpected error: %v", err)
|
|
}
|
|
storedCR, _ = etcdCR.(*unstructured.Unstructured)
|
|
if storedCR.GetGeneration() != 2 {
|
|
t.Fatalf("Unexpected generation, spec: %v", storedCR.GetGeneration())
|
|
}
|
|
|
|
}
|
|
|
|
func TestCategories(t *testing.T) {
|
|
storage, server := newStorage(t)
|
|
defer server.Terminate(t)
|
|
defer storage.CustomResource.Store.DestroyFunc()
|
|
|
|
expected := []string{"all"}
|
|
actual := storage.CustomResource.Categories()
|
|
ok := reflect.DeepEqual(actual, expected)
|
|
if !ok {
|
|
t.Errorf("categories are not equal. expected = %v actual = %v", expected, actual)
|
|
}
|
|
}
|
|
|
|
func TestColumns(t *testing.T) {
|
|
storage, server := newStorage(t)
|
|
defer server.Terminate(t)
|
|
defer storage.CustomResource.Store.DestroyFunc()
|
|
|
|
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault)
|
|
key := "/noxus/" + metav1.NamespaceDefault + "/foo"
|
|
validCustomResource := validNewCustomResource()
|
|
if err := storage.CustomResource.Storage.Create(ctx, key, validCustomResource, nil, 0, false); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
gottenList, err := storage.CustomResource.List(ctx, &metainternal.ListOptions{})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
tbl, err := storage.CustomResource.ConvertToTable(ctx, gottenList, &metav1beta1.TableOptions{})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
expectedColumns := []struct {
|
|
Name, Type string
|
|
}{
|
|
{"Name", "string"},
|
|
{"Age", "date"},
|
|
{"Replicas", "integer"},
|
|
{"Missing", "string"},
|
|
{"Invalid", "integer"},
|
|
{"String", "string"},
|
|
{"StringFloat64", "string"},
|
|
{"StringInt64", "string"},
|
|
{"StringBool", "string"},
|
|
{"Float64", "number"},
|
|
{"Bool", "boolean"},
|
|
}
|
|
if len(tbl.ColumnDefinitions) != len(expectedColumns) {
|
|
t.Fatalf("got %d columns, expected %d. Got: %+v", len(tbl.ColumnDefinitions), len(expectedColumns), tbl.ColumnDefinitions)
|
|
}
|
|
for i, d := range tbl.ColumnDefinitions {
|
|
if d.Name != expectedColumns[i].Name {
|
|
t.Errorf("got column %d name %q, expected %q", i, d.Name, expectedColumns[i].Name)
|
|
}
|
|
if d.Type != expectedColumns[i].Type {
|
|
t.Errorf("got column %d type %q, expected %q", i, d.Type, expectedColumns[i].Type)
|
|
}
|
|
}
|
|
|
|
expectedRows := [][]interface{}{
|
|
{
|
|
"foo",
|
|
"12h",
|
|
int64(7),
|
|
nil,
|
|
nil,
|
|
"string",
|
|
"3.1415926",
|
|
"7",
|
|
"true",
|
|
float64(3.1415926),
|
|
true,
|
|
},
|
|
}
|
|
for i, r := range tbl.Rows {
|
|
if !reflect.DeepEqual(r.Cells, expectedRows[i]) {
|
|
t.Errorf("got row %d with cells %#v, expected %#v", i, r.Cells, expectedRows[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestStatusUpdate(t *testing.T) {
|
|
storage, server := newStorage(t)
|
|
defer server.Terminate(t)
|
|
defer storage.CustomResource.Store.DestroyFunc()
|
|
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault)
|
|
key := "/noxus/" + metav1.NamespaceDefault + "/foo"
|
|
validCustomResource := validNewCustomResource()
|
|
if err := storage.CustomResource.Storage.Create(ctx, key, validCustomResource, nil, 0, false); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
gottenObj, err := storage.CustomResource.Get(ctx, "foo", &metav1.GetOptions{})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
update := gottenObj.(*unstructured.Unstructured)
|
|
updateContent := update.Object
|
|
updateContent["status"] = map[string]interface{}{
|
|
"replicas": int64(7),
|
|
}
|
|
|
|
if _, _, err := storage.Status.Update(ctx, update.GetName(), rest.DefaultUpdatedObjectInfo(update), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
obj, err := storage.CustomResource.Get(ctx, "foo", &metav1.GetOptions{})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
cr, ok := obj.(*unstructured.Unstructured)
|
|
if !ok {
|
|
t.Fatal("unexpected error: custom resource should be of type Unstructured")
|
|
}
|
|
content := cr.UnstructuredContent()
|
|
|
|
spec := content["spec"].(map[string]interface{})
|
|
status := content["status"].(map[string]interface{})
|
|
|
|
if spec["replicas"].(int64) != 7 {
|
|
t.Errorf("we expected .spec.replicas to not be updated but it was updated to %v", spec["replicas"].(int64))
|
|
}
|
|
if status["replicas"].(int64) != 7 {
|
|
t.Errorf("we expected .status.replicas to be updated to %d but it was %v", 7, status["replicas"].(int64))
|
|
}
|
|
}
|
|
|
|
func TestScaleGet(t *testing.T) {
|
|
storage, server := newStorage(t)
|
|
defer server.Terminate(t)
|
|
defer storage.CustomResource.Store.DestroyFunc()
|
|
|
|
name := "foo"
|
|
|
|
var cr unstructured.Unstructured
|
|
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault)
|
|
key := "/noxus/" + metav1.NamespaceDefault + "/" + name
|
|
if err := storage.CustomResource.Storage.Create(ctx, key, &validCustomResource, &cr, 0, false); err != nil {
|
|
t.Fatalf("error setting new custom resource (key: %s) %v: %v", key, validCustomResource, err)
|
|
}
|
|
|
|
want := &autoscalingv1.Scale{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: cr.GetName(),
|
|
Namespace: metav1.NamespaceDefault,
|
|
UID: cr.GetUID(),
|
|
ResourceVersion: cr.GetResourceVersion(),
|
|
CreationTimestamp: cr.GetCreationTimestamp(),
|
|
},
|
|
Spec: autoscalingv1.ScaleSpec{
|
|
Replicas: int32(7),
|
|
},
|
|
}
|
|
|
|
obj, err := storage.Scale.Get(ctx, name, &metav1.GetOptions{})
|
|
if err != nil {
|
|
t.Fatalf("error fetching scale for %s: %v", name, err)
|
|
}
|
|
|
|
got := obj.(*autoscalingv1.Scale)
|
|
if !apiequality.Semantic.DeepEqual(got, want) {
|
|
t.Errorf("unexpected scale: %s", diff.ObjectDiff(got, want))
|
|
}
|
|
}
|
|
|
|
func TestScaleGetWithoutSpecReplicas(t *testing.T) {
|
|
storage, server := newStorage(t)
|
|
defer server.Terminate(t)
|
|
defer storage.CustomResource.Store.DestroyFunc()
|
|
|
|
name := "foo"
|
|
|
|
var cr unstructured.Unstructured
|
|
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault)
|
|
key := "/noxus/" + metav1.NamespaceDefault + "/" + name
|
|
withoutSpecReplicas := validCustomResource.DeepCopy()
|
|
unstructured.RemoveNestedField(withoutSpecReplicas.Object, "spec", "replicas")
|
|
if err := storage.CustomResource.Storage.Create(ctx, key, withoutSpecReplicas, &cr, 0, false); err != nil {
|
|
t.Fatalf("error setting new custom resource (key: %s) %v: %v", key, withoutSpecReplicas, err)
|
|
}
|
|
|
|
_, err := storage.Scale.Get(ctx, name, &metav1.GetOptions{})
|
|
if err == nil {
|
|
t.Fatalf("error expected for %s", name)
|
|
}
|
|
if expected := `the spec replicas field ".spec.replicas" does not exist`; !strings.Contains(err.Error(), expected) {
|
|
t.Fatalf("expected error string %q, got: %v", expected, err)
|
|
}
|
|
}
|
|
|
|
func TestScaleUpdate(t *testing.T) {
|
|
storage, server := newStorage(t)
|
|
defer server.Terminate(t)
|
|
defer storage.CustomResource.Store.DestroyFunc()
|
|
|
|
name := "foo"
|
|
|
|
var cr unstructured.Unstructured
|
|
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault)
|
|
key := "/noxus/" + metav1.NamespaceDefault + "/" + name
|
|
if err := storage.CustomResource.Storage.Create(ctx, key, &validCustomResource, &cr, 0, false); err != nil {
|
|
t.Fatalf("error setting new custom resource (key: %s) %v: %v", key, validCustomResource, err)
|
|
}
|
|
|
|
obj, err := storage.Scale.Get(ctx, name, &metav1.GetOptions{})
|
|
if err != nil {
|
|
t.Fatalf("error fetching scale for %s: %v", name, err)
|
|
}
|
|
scale, ok := obj.(*autoscalingv1.Scale)
|
|
if !ok {
|
|
t.Fatalf("%v is not of the type autoscalingv1.Scale", scale)
|
|
}
|
|
|
|
replicas := 12
|
|
update := autoscalingv1.Scale{
|
|
ObjectMeta: scale.ObjectMeta,
|
|
Spec: autoscalingv1.ScaleSpec{
|
|
Replicas: int32(replicas),
|
|
},
|
|
}
|
|
|
|
if _, _, err := storage.Scale.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}); err != nil {
|
|
t.Fatalf("error updating scale %v: %v", update, err)
|
|
}
|
|
|
|
obj, err = storage.Scale.Get(ctx, name, &metav1.GetOptions{})
|
|
if err != nil {
|
|
t.Fatalf("error fetching scale for %s: %v", name, err)
|
|
}
|
|
scale = obj.(*autoscalingv1.Scale)
|
|
if scale.Spec.Replicas != int32(replicas) {
|
|
t.Errorf("wrong replicas count: expected: %d got: %d", replicas, scale.Spec.Replicas)
|
|
}
|
|
|
|
update.ResourceVersion = scale.ResourceVersion
|
|
update.Spec.Replicas = 15
|
|
|
|
if _, _, err = storage.Scale.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}); err != nil && !errors.IsConflict(err) {
|
|
t.Fatalf("unexpected error, expecting an update conflict but got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestScaleUpdateWithoutSpecReplicas(t *testing.T) {
|
|
storage, server := newStorage(t)
|
|
defer server.Terminate(t)
|
|
defer storage.CustomResource.Store.DestroyFunc()
|
|
|
|
name := "foo"
|
|
|
|
var cr unstructured.Unstructured
|
|
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault)
|
|
key := "/noxus/" + metav1.NamespaceDefault + "/" + name
|
|
withoutSpecReplicas := validCustomResource.DeepCopy()
|
|
unstructured.RemoveNestedField(withoutSpecReplicas.Object, "spec", "replicas")
|
|
if err := storage.CustomResource.Storage.Create(ctx, key, withoutSpecReplicas, &cr, 0, false); err != nil {
|
|
t.Fatalf("error setting new custom resource (key: %s) %v: %v", key, withoutSpecReplicas, err)
|
|
}
|
|
|
|
replicas := 12
|
|
update := autoscalingv1.Scale{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
ResourceVersion: cr.GetResourceVersion(),
|
|
},
|
|
Spec: autoscalingv1.ScaleSpec{
|
|
Replicas: int32(replicas),
|
|
},
|
|
}
|
|
|
|
if _, _, err := storage.Scale.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}); err != nil {
|
|
t.Fatalf("error updating scale %v: %v", update, err)
|
|
}
|
|
|
|
obj, err := storage.Scale.Get(ctx, name, &metav1.GetOptions{})
|
|
if err != nil {
|
|
t.Fatalf("error fetching scale for %s: %v", name, err)
|
|
}
|
|
scale := obj.(*autoscalingv1.Scale)
|
|
if scale.Spec.Replicas != int32(replicas) {
|
|
t.Errorf("wrong replicas count: expected: %d got: %d", replicas, scale.Spec.Replicas)
|
|
}
|
|
}
|
|
|
|
type unstructuredJsonCodec struct{}
|
|
|
|
func (c unstructuredJsonCodec) Decode(data []byte, defaults *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) {
|
|
obj := into.(*unstructured.Unstructured)
|
|
err := obj.UnmarshalJSON(data)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
gvk := obj.GroupVersionKind()
|
|
return obj, &gvk, nil
|
|
}
|
|
|
|
func (c unstructuredJsonCodec) Encode(obj runtime.Object, w io.Writer) error {
|
|
u := obj.(*unstructured.Unstructured)
|
|
bs, err := u.MarshalJSON()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
w.Write(bs)
|
|
return nil
|
|
}
|
|
|
|
func setSpecReplicas(u *unstructured.Unstructured, replicas int64) {
|
|
setNestedField(u, replicas, "spec", "replicas")
|
|
}
|
|
|
|
func getSpecReplicas(u *unstructured.Unstructured) int64 {
|
|
val, found, err := unstructured.NestedInt64(u.Object, "spec", "replicas")
|
|
if !found || err != nil {
|
|
return 0
|
|
}
|
|
return val
|
|
}
|
|
|
|
func setStatusReplicas(u *unstructured.Unstructured, replicas int64) {
|
|
setNestedField(u, replicas, "status", "replicas")
|
|
}
|
|
|
|
func getStatusReplicas(u *unstructured.Unstructured) int64 {
|
|
val, found, err := unstructured.NestedInt64(u.Object, "status", "replicas")
|
|
if !found || err != nil {
|
|
return 0
|
|
}
|
|
return val
|
|
}
|
|
|
|
func setNestedField(u *unstructured.Unstructured, value interface{}, fields ...string) {
|
|
if u.Object == nil {
|
|
u.Object = make(map[string]interface{})
|
|
}
|
|
unstructured.SetNestedField(u.Object, value, fields...)
|
|
}
|