mirror of https://github.com/k3s-io/k3s
349 lines
11 KiB
Go
349 lines
11 KiB
Go
/*
|
|
Copyright 2015 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 v1_test
|
|
|
|
import (
|
|
"encoding/json"
|
|
"math/rand"
|
|
"net/url"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
appsv1 "k8s.io/api/apps/v1"
|
|
"k8s.io/api/core/v1"
|
|
"k8s.io/apimachinery/pkg/api/apitesting/fuzzer"
|
|
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
|
"k8s.io/apimachinery/pkg/api/resource"
|
|
metafuzzer "k8s.io/apimachinery/pkg/apis/meta/fuzzer"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/util/diff"
|
|
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
|
apps "k8s.io/kubernetes/pkg/apis/apps"
|
|
"k8s.io/kubernetes/pkg/apis/core"
|
|
corefuzzer "k8s.io/kubernetes/pkg/apis/core/fuzzer"
|
|
corev1 "k8s.io/kubernetes/pkg/apis/core/v1"
|
|
utilpointer "k8s.io/utils/pointer"
|
|
|
|
// enforce that all types are installed
|
|
_ "k8s.io/kubernetes/pkg/api/testapi"
|
|
)
|
|
|
|
func TestPodLogOptions(t *testing.T) {
|
|
sinceSeconds := int64(1)
|
|
sinceTime := metav1.NewTime(time.Date(2000, 1, 1, 12, 34, 56, 0, time.UTC).Local())
|
|
tailLines := int64(2)
|
|
limitBytes := int64(3)
|
|
|
|
versionedLogOptions := &v1.PodLogOptions{
|
|
Container: "mycontainer",
|
|
Follow: true,
|
|
Previous: true,
|
|
SinceSeconds: &sinceSeconds,
|
|
SinceTime: &sinceTime,
|
|
Timestamps: true,
|
|
TailLines: &tailLines,
|
|
LimitBytes: &limitBytes,
|
|
}
|
|
unversionedLogOptions := &core.PodLogOptions{
|
|
Container: "mycontainer",
|
|
Follow: true,
|
|
Previous: true,
|
|
SinceSeconds: &sinceSeconds,
|
|
SinceTime: &sinceTime,
|
|
Timestamps: true,
|
|
TailLines: &tailLines,
|
|
LimitBytes: &limitBytes,
|
|
}
|
|
expectedParameters := url.Values{
|
|
"container": {"mycontainer"},
|
|
"follow": {"true"},
|
|
"previous": {"true"},
|
|
"sinceSeconds": {"1"},
|
|
"sinceTime": {"2000-01-01T12:34:56Z"},
|
|
"timestamps": {"true"},
|
|
"tailLines": {"2"},
|
|
"limitBytes": {"3"},
|
|
}
|
|
|
|
codec := runtime.NewParameterCodec(legacyscheme.Scheme)
|
|
|
|
// unversioned -> query params
|
|
{
|
|
actualParameters, err := codec.EncodeParameters(unversionedLogOptions, v1.SchemeGroupVersion)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !reflect.DeepEqual(actualParameters, expectedParameters) {
|
|
t.Fatalf("Expected\n%#v\ngot\n%#v", expectedParameters, actualParameters)
|
|
}
|
|
}
|
|
|
|
// versioned -> query params
|
|
{
|
|
actualParameters, err := codec.EncodeParameters(versionedLogOptions, v1.SchemeGroupVersion)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !reflect.DeepEqual(actualParameters, expectedParameters) {
|
|
t.Fatalf("Expected\n%#v\ngot\n%#v", expectedParameters, actualParameters)
|
|
}
|
|
}
|
|
|
|
// query params -> versioned
|
|
{
|
|
convertedLogOptions := &v1.PodLogOptions{}
|
|
err := codec.DecodeParameters(expectedParameters, v1.SchemeGroupVersion, convertedLogOptions)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !reflect.DeepEqual(convertedLogOptions, versionedLogOptions) {
|
|
t.Fatalf("Unexpected deserialization:\n%s", diff.ObjectGoPrintSideBySide(versionedLogOptions, convertedLogOptions))
|
|
}
|
|
}
|
|
|
|
// query params -> unversioned
|
|
{
|
|
convertedLogOptions := &core.PodLogOptions{}
|
|
err := codec.DecodeParameters(expectedParameters, v1.SchemeGroupVersion, convertedLogOptions)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !reflect.DeepEqual(convertedLogOptions, unversionedLogOptions) {
|
|
t.Fatalf("Unexpected deserialization:\n%s", diff.ObjectGoPrintSideBySide(unversionedLogOptions, convertedLogOptions))
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestPodSpecConversion tests that v1.ServiceAccount is an alias for
|
|
// ServiceAccountName.
|
|
func TestPodSpecConversion(t *testing.T) {
|
|
name, other := "foo", "bar"
|
|
|
|
// Test internal -> v1. Should have both alias (DeprecatedServiceAccount)
|
|
// and new field (ServiceAccountName).
|
|
i := &core.PodSpec{
|
|
ServiceAccountName: name,
|
|
}
|
|
v := v1.PodSpec{}
|
|
if err := legacyscheme.Scheme.Convert(i, &v, nil); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if v.ServiceAccountName != name {
|
|
t.Fatalf("want v1.ServiceAccountName %q, got %q", name, v.ServiceAccountName)
|
|
}
|
|
if v.DeprecatedServiceAccount != name {
|
|
t.Fatalf("want v1.DeprecatedServiceAccount %q, got %q", name, v.DeprecatedServiceAccount)
|
|
}
|
|
|
|
// Test v1 -> internal. Either DeprecatedServiceAccount, ServiceAccountName,
|
|
// or both should translate to ServiceAccountName. ServiceAccountName wins
|
|
// if both are set.
|
|
testCases := []*v1.PodSpec{
|
|
// New
|
|
{ServiceAccountName: name},
|
|
// Alias
|
|
{DeprecatedServiceAccount: name},
|
|
// Both: same
|
|
{ServiceAccountName: name, DeprecatedServiceAccount: name},
|
|
// Both: different
|
|
{ServiceAccountName: name, DeprecatedServiceAccount: other},
|
|
}
|
|
for k, v := range testCases {
|
|
got := core.PodSpec{}
|
|
err := legacyscheme.Scheme.Convert(v, &got, nil)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error for case %d: %v", k, err)
|
|
}
|
|
if got.ServiceAccountName != name {
|
|
t.Fatalf("want core.ServiceAccountName %q, got %q", name, got.ServiceAccountName)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestResourceListConversion(t *testing.T) {
|
|
bigMilliQuantity := resource.NewQuantity(resource.MaxMilliValue, resource.DecimalSI)
|
|
bigMilliQuantity.Add(resource.MustParse("12345m"))
|
|
|
|
tests := []struct {
|
|
input v1.ResourceList
|
|
expected core.ResourceList
|
|
}{
|
|
{ // No changes necessary.
|
|
input: v1.ResourceList{
|
|
v1.ResourceMemory: resource.MustParse("30M"),
|
|
v1.ResourceCPU: resource.MustParse("100m"),
|
|
v1.ResourceStorage: resource.MustParse("1G"),
|
|
},
|
|
expected: core.ResourceList{
|
|
core.ResourceMemory: resource.MustParse("30M"),
|
|
core.ResourceCPU: resource.MustParse("100m"),
|
|
core.ResourceStorage: resource.MustParse("1G"),
|
|
},
|
|
},
|
|
{ // Nano-scale values should be rounded up to milli-scale.
|
|
input: v1.ResourceList{
|
|
v1.ResourceCPU: resource.MustParse("3.000023m"),
|
|
v1.ResourceMemory: resource.MustParse("500.000050m"),
|
|
},
|
|
expected: core.ResourceList{
|
|
core.ResourceCPU: resource.MustParse("4m"),
|
|
core.ResourceMemory: resource.MustParse("501m"),
|
|
},
|
|
},
|
|
{ // Large values should still be accurate.
|
|
input: v1.ResourceList{
|
|
v1.ResourceCPU: *bigMilliQuantity.Copy(),
|
|
v1.ResourceStorage: *bigMilliQuantity.Copy(),
|
|
},
|
|
expected: core.ResourceList{
|
|
core.ResourceCPU: *bigMilliQuantity.Copy(),
|
|
core.ResourceStorage: *bigMilliQuantity.Copy(),
|
|
},
|
|
},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
output := core.ResourceList{}
|
|
|
|
// defaulting is a separate step from conversion that is applied when reading from the API or from etcd.
|
|
// perform that step explicitly.
|
|
corev1.SetDefaults_ResourceList(&test.input)
|
|
|
|
err := legacyscheme.Scheme.Convert(&test.input, &output, nil)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error for case %d: %v", i, err)
|
|
}
|
|
if !apiequality.Semantic.DeepEqual(test.expected, output) {
|
|
t.Errorf("unexpected conversion for case %d: Expected\n%+v;\nGot\n%+v", i, test.expected, output)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestReplicationControllerConversion(t *testing.T) {
|
|
// If we start with a RC, we should always have round-trip fidelity.
|
|
inputs := []*v1.ReplicationController{
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "name",
|
|
Namespace: "namespace",
|
|
},
|
|
Spec: v1.ReplicationControllerSpec{
|
|
Replicas: utilpointer.Int32Ptr(1),
|
|
MinReadySeconds: 32,
|
|
Selector: map[string]string{"foo": "bar", "bar": "foo"},
|
|
Template: &v1.PodTemplateSpec{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Labels: map[string]string{"foo": "bar", "bar": "foo"},
|
|
},
|
|
Spec: v1.PodSpec{
|
|
Containers: []v1.Container{
|
|
{
|
|
Name: "container",
|
|
Image: "image",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Status: v1.ReplicationControllerStatus{
|
|
Replicas: 1,
|
|
FullyLabeledReplicas: 2,
|
|
ReadyReplicas: 3,
|
|
AvailableReplicas: 4,
|
|
ObservedGeneration: 5,
|
|
Conditions: []v1.ReplicationControllerCondition{
|
|
{
|
|
Type: v1.ReplicationControllerReplicaFailure,
|
|
Status: v1.ConditionTrue,
|
|
LastTransitionTime: metav1.NewTime(time.Unix(123456789, 0)),
|
|
Reason: "Reason",
|
|
Message: "Message",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
// Add some fuzzed RCs.
|
|
apiObjectFuzzer := fuzzer.FuzzerFor(fuzzer.MergeFuzzerFuncs(metafuzzer.Funcs, corefuzzer.Funcs), rand.NewSource(152), legacyscheme.Codecs)
|
|
for i := 0; i < 100; i++ {
|
|
rc := &v1.ReplicationController{}
|
|
apiObjectFuzzer.Fuzz(rc)
|
|
// Sometimes the fuzzer decides to leave Spec.Template nil.
|
|
// We can't support that because Spec.Template is not a pointer in RS,
|
|
// so it will round-trip as non-nil but empty.
|
|
if rc.Spec.Template == nil {
|
|
rc.Spec.Template = &v1.PodTemplateSpec{}
|
|
}
|
|
// Sometimes the fuzzer decides to insert an empty label key.
|
|
// This doesn't round-trip properly because it's invalid.
|
|
if rc.Spec.Selector != nil {
|
|
delete(rc.Spec.Selector, "")
|
|
}
|
|
inputs = append(inputs, rc)
|
|
}
|
|
|
|
// Round-trip the input RCs before converting to RS.
|
|
for i := range inputs {
|
|
inputs[i] = roundTrip(t, inputs[i]).(*v1.ReplicationController)
|
|
}
|
|
|
|
for _, in := range inputs {
|
|
rs := &apps.ReplicaSet{}
|
|
// Use in.DeepCopy() to avoid sharing pointers with `in`.
|
|
if err := corev1.Convert_v1_ReplicationController_To_apps_ReplicaSet(in.DeepCopy(), rs, nil); err != nil {
|
|
t.Errorf("can't convert RC to RS: %v", err)
|
|
continue
|
|
}
|
|
// Round-trip RS before converting back to RC.
|
|
rs = roundTripRS(t, rs)
|
|
out := &v1.ReplicationController{}
|
|
if err := corev1.Convert_apps_ReplicaSet_To_v1_ReplicationController(rs, out, nil); err != nil {
|
|
t.Errorf("can't convert RS to RC: %v", err)
|
|
continue
|
|
}
|
|
if !apiequality.Semantic.DeepEqual(in, out) {
|
|
instr, _ := json.MarshalIndent(in, "", " ")
|
|
outstr, _ := json.MarshalIndent(out, "", " ")
|
|
t.Errorf("RC-RS conversion round-trip failed:\nin:\n%s\nout:\n%s", instr, outstr)
|
|
}
|
|
}
|
|
}
|
|
|
|
func roundTripRS(t *testing.T, rs *apps.ReplicaSet) *apps.ReplicaSet {
|
|
codec := legacyscheme.Codecs.LegacyCodec(appsv1.SchemeGroupVersion)
|
|
data, err := runtime.Encode(codec, rs)
|
|
if err != nil {
|
|
t.Errorf("%v\n %#v", err, rs)
|
|
return nil
|
|
}
|
|
obj2, err := runtime.Decode(codec, data)
|
|
if err != nil {
|
|
t.Errorf("%v\nData: %s\nSource: %#v", err, string(data), rs)
|
|
return nil
|
|
}
|
|
obj3 := &apps.ReplicaSet{}
|
|
err = legacyscheme.Scheme.Convert(obj2, obj3, nil)
|
|
if err != nil {
|
|
t.Errorf("%v\nSource: %#v", err, obj2)
|
|
return nil
|
|
}
|
|
return obj3
|
|
}
|