Compare commits

...

5 Commits

Author SHA1 Message Date
Kubernetes Publisher
8a233ea759 Update dependencies to v0.22.0-beta.2 tag 2021-07-15 02:27:42 +00:00
Kubernetes Publisher
7a90b08589 Merge pull request #102928 from dprotaso/dynamic-client-backwards-compatible
Simplify construction of the fake dynamic client

Kubernetes-commit: fc4e7c17f4aead0bc953be03178c4e3caee1015f
2021-07-08 21:48:10 +00:00
Kubernetes Publisher
f0bc45ffb4 Merge pull request #98817 from alculquicondor/job-completion-api
Add Job.status.uncountedTerminatedPods for Job tracking

Kubernetes-commit: b76549665070beb2cc1a0410a6c7f66d8007f76b
2021-07-08 21:48:09 +00:00
dprotaso
c6c0ca0e1e Simplify use of the fake dynamic client
With the introduction of GVK to the fake dynamic client it made using
the fake much more cumbersome.

Specifically:
- requires manual registration of list types
- mismatch between scheme types and passed in fixtures would result in errors

The PR changes the constructor method NewSimpleDynamicClient to do the following:
- rewire the schemes to unstructured types
- typed fixtures are converted to unstructured types
- automatically register fixture gvks with the scheme

This should make the dynamic client 'flexible' with it's inputs like it was
before

Kubernetes-commit: 418fa71b6b1d1fba930daaba1f8ecf55070b4bdf
2021-06-16 14:51:00 -04:00
Aldo Culquicondor
3bb4101a36 Add Job.status.uncountedPodUIDs
For tracking Job Pods that have finished but are not yet counted as failed or succeeded

And feature gate JobTrackingWithFinalizers

Change-Id: I3e080f3ec090922640384b692e88eaf9a544d3b5

Kubernetes-commit: bb56a0bd04891ae0a4aebeaeb4d145b32d9ad2d9
2021-01-18 13:36:03 -05:00
8 changed files with 331 additions and 16 deletions

View File

@@ -25,13 +25,14 @@ import (
// JobStatusApplyConfiguration represents an declarative configuration of the JobStatus type for use
// with apply.
type JobStatusApplyConfiguration struct {
Conditions []JobConditionApplyConfiguration `json:"conditions,omitempty"`
StartTime *metav1.Time `json:"startTime,omitempty"`
CompletionTime *metav1.Time `json:"completionTime,omitempty"`
Active *int32 `json:"active,omitempty"`
Succeeded *int32 `json:"succeeded,omitempty"`
Failed *int32 `json:"failed,omitempty"`
CompletedIndexes *string `json:"completedIndexes,omitempty"`
Conditions []JobConditionApplyConfiguration `json:"conditions,omitempty"`
StartTime *metav1.Time `json:"startTime,omitempty"`
CompletionTime *metav1.Time `json:"completionTime,omitempty"`
Active *int32 `json:"active,omitempty"`
Succeeded *int32 `json:"succeeded,omitempty"`
Failed *int32 `json:"failed,omitempty"`
CompletedIndexes *string `json:"completedIndexes,omitempty"`
UncountedTerminatedPods *UncountedTerminatedPodsApplyConfiguration `json:"uncountedTerminatedPods,omitempty"`
}
// JobStatusApplyConfiguration constructs an declarative configuration of the JobStatus type for use with
@@ -100,3 +101,11 @@ func (b *JobStatusApplyConfiguration) WithCompletedIndexes(value string) *JobSta
b.CompletedIndexes = &value
return b
}
// WithUncountedTerminatedPods sets the UncountedTerminatedPods field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the UncountedTerminatedPods field is set to the value of the last call.
func (b *JobStatusApplyConfiguration) WithUncountedTerminatedPods(value *UncountedTerminatedPodsApplyConfiguration) *JobStatusApplyConfiguration {
b.UncountedTerminatedPods = value
return b
}

View File

@@ -0,0 +1,56 @@
/*
Copyright 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.
*/
// Code generated by applyconfiguration-gen. DO NOT EDIT.
package v1
import (
types "k8s.io/apimachinery/pkg/types"
)
// UncountedTerminatedPodsApplyConfiguration represents an declarative configuration of the UncountedTerminatedPods type for use
// with apply.
type UncountedTerminatedPodsApplyConfiguration struct {
Succeeded []types.UID `json:"succeeded,omitempty"`
Failed []types.UID `json:"failed,omitempty"`
}
// UncountedTerminatedPodsApplyConfiguration constructs an declarative configuration of the UncountedTerminatedPods type for use with
// apply.
func UncountedTerminatedPods() *UncountedTerminatedPodsApplyConfiguration {
return &UncountedTerminatedPodsApplyConfiguration{}
}
// WithSucceeded adds the given value to the Succeeded field in the declarative configuration
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
// If called multiple times, values provided by each call will be appended to the Succeeded field.
func (b *UncountedTerminatedPodsApplyConfiguration) WithSucceeded(values ...types.UID) *UncountedTerminatedPodsApplyConfiguration {
for i := range values {
b.Succeeded = append(b.Succeeded, values[i])
}
return b
}
// WithFailed adds the given value to the Failed field in the declarative configuration
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
// If called multiple times, values provided by each call will be appended to the Failed field.
func (b *UncountedTerminatedPodsApplyConfiguration) WithFailed(values ...types.UID) *UncountedTerminatedPodsApplyConfiguration {
for i := range values {
b.Failed = append(b.Failed, values[i])
}
return b
}

View File

@@ -2710,6 +2710,9 @@ var schemaYAML = typed.YAMLObject(`types:
- name: succeeded
type:
scalar: numeric
- name: uncountedTerminatedPods
type:
namedType: io.k8s.api.batch.v1.UncountedTerminatedPods
- name: io.k8s.api.batch.v1.JobTemplateSpec
map:
fields:
@@ -2721,6 +2724,21 @@ var schemaYAML = typed.YAMLObject(`types:
type:
namedType: io.k8s.api.batch.v1.JobSpec
default: {}
- name: io.k8s.api.batch.v1.UncountedTerminatedPods
map:
fields:
- name: failed
type:
list:
elementType:
scalar: string
elementRelationship: associative
- name: succeeded
type:
list:
elementType:
scalar: string
elementRelationship: associative
- name: io.k8s.api.batch.v1beta1.CronJob
map:
fields:

View File

@@ -395,6 +395,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} {
return &applyconfigurationsbatchv1.JobStatusApplyConfiguration{}
case batchv1.SchemeGroupVersion.WithKind("JobTemplateSpec"):
return &applyconfigurationsbatchv1.JobTemplateSpecApplyConfiguration{}
case batchv1.SchemeGroupVersion.WithKind("UncountedTerminatedPods"):
return &applyconfigurationsbatchv1.UncountedTerminatedPodsApplyConfiguration{}
// Group=batch, Version=v1beta1
case batchv1beta1.SchemeGroupVersion.WithKind("CronJob"):

View File

@@ -35,7 +35,35 @@ import (
)
func NewSimpleDynamicClient(scheme *runtime.Scheme, objects ...runtime.Object) *FakeDynamicClient {
return NewSimpleDynamicClientWithCustomListKinds(scheme, nil, objects...)
unstructuredScheme := runtime.NewScheme()
for gvk := range scheme.AllKnownTypes() {
if unstructuredScheme.Recognizes(gvk) {
continue
}
if strings.HasSuffix(gvk.Kind, "List") {
unstructuredScheme.AddKnownTypeWithName(gvk, &unstructured.UnstructuredList{})
continue
}
unstructuredScheme.AddKnownTypeWithName(gvk, &unstructured.Unstructured{})
}
objects, err := convertObjectsToUnstructured(scheme, objects)
if err != nil {
panic(err)
}
for _, obj := range objects {
gvk := obj.GetObjectKind().GroupVersionKind()
if !unstructuredScheme.Recognizes(gvk) {
unstructuredScheme.AddKnownTypeWithName(gvk, &unstructured.Unstructured{})
}
gvk.Kind += "List"
if !unstructuredScheme.Recognizes(gvk) {
unstructuredScheme.AddKnownTypeWithName(gvk, &unstructured.UnstructuredList{})
}
}
return NewSimpleDynamicClientWithCustomListKinds(unstructuredScheme, nil, objects...)
}
// NewSimpleDynamicClientWithCustomListKinds try not to use this. In general you want to have the scheme have the List types registered
@@ -425,3 +453,41 @@ func (c *dynamicResourceClient) Patch(ctx context.Context, name string, pt types
}
return ret, err
}
func convertObjectsToUnstructured(s *runtime.Scheme, objs []runtime.Object) ([]runtime.Object, error) {
ul := make([]runtime.Object, 0, len(objs))
for _, obj := range objs {
u, err := convertToUnstructured(s, obj)
if err != nil {
return nil, err
}
ul = append(ul, u)
}
return ul, nil
}
func convertToUnstructured(s *runtime.Scheme, obj runtime.Object) (runtime.Object, error) {
var (
err error
u unstructured.Unstructured
)
u.Object, err = runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
if err != nil {
return nil, fmt.Errorf("failed to convert to unstructured: %w", err)
}
gvk := u.GroupVersionKind()
if gvk.Group == "" || gvk.Kind == "" {
gvks, _, err := s.ObjectKinds(obj)
if err != nil {
return nil, fmt.Errorf("failed to convert to unstructured - unable to get GVK %w", err)
}
apiv, k := gvks[0].ToAPIVersionAndKind()
u.SetAPIVersion(apiv)
u.SetKind(k)
}
return &u, nil
}

View File

@@ -21,6 +21,7 @@ import (
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
"k8s.io/apimachinery/pkg/api/equality"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -303,3 +304,166 @@ func TestPatch(t *testing.T) {
t.Run(tc.name, tc.runner)
}
}
// This test ensures list works when the fake dynamic client is seeded with a typed scheme and
// unstructured type fixtures
func TestListWithUnstructuredObjectsAndTypedScheme(t *testing.T) {
gvr := schema.GroupVersionResource{Group: testGroup, Version: testVersion, Resource: testResource}
gvk := gvr.GroupVersion().WithKind(testKind)
listGVK := gvk
listGVK.Kind += "List"
u := unstructured.Unstructured{}
u.SetGroupVersionKind(gvk)
u.SetName("name")
u.SetNamespace("namespace")
typedScheme := runtime.NewScheme()
typedScheme.AddKnownTypeWithName(gvk, &mockResource{})
typedScheme.AddKnownTypeWithName(listGVK, &mockResourceList{})
client := NewSimpleDynamicClient(typedScheme, &u)
list, err := client.Resource(gvr).Namespace("namespace").List(context.Background(), metav1.ListOptions{})
if err != nil {
t.Error("error listing", err)
}
expectedList := &unstructured.UnstructuredList{}
expectedList.SetGroupVersionKind(listGVK)
expectedList.SetResourceVersion("") // by product of the fake setting resource version
expectedList.Items = append(expectedList.Items, u)
if diff := cmp.Diff(expectedList, list); diff != "" {
t.Fatal("unexpected diff (-want, +got): ", diff)
}
}
func TestListWithNoFixturesAndTypedScheme(t *testing.T) {
gvr := schema.GroupVersionResource{Group: testGroup, Version: testVersion, Resource: testResource}
gvk := gvr.GroupVersion().WithKind(testKind)
listGVK := gvk
listGVK.Kind += "List"
typedScheme := runtime.NewScheme()
typedScheme.AddKnownTypeWithName(gvk, &mockResource{})
typedScheme.AddKnownTypeWithName(listGVK, &mockResourceList{})
client := NewSimpleDynamicClient(typedScheme)
list, err := client.Resource(gvr).Namespace("namespace").List(context.Background(), metav1.ListOptions{})
if err != nil {
t.Error("error listing", err)
}
expectedList := &unstructured.UnstructuredList{}
expectedList.SetGroupVersionKind(listGVK)
expectedList.SetResourceVersion("") // by product of the fake setting resource version
if diff := cmp.Diff(expectedList, list); diff != "" {
t.Fatal("unexpected diff (-want, +got): ", diff)
}
}
// This test ensures list works when the dynamic client is seeded with an empty scheme and
// unstructured typed fixtures
func TestListWithNoScheme(t *testing.T) {
gvr := schema.GroupVersionResource{Group: testGroup, Version: testVersion, Resource: testResource}
gvk := gvr.GroupVersion().WithKind(testKind)
listGVK := gvk
listGVK.Kind += "List"
u := unstructured.Unstructured{}
u.SetGroupVersionKind(gvk)
u.SetName("name")
u.SetNamespace("namespace")
emptyScheme := runtime.NewScheme()
client := NewSimpleDynamicClient(emptyScheme, &u)
list, err := client.Resource(gvr).Namespace("namespace").List(context.Background(), metav1.ListOptions{})
if err != nil {
t.Error("error listing", err)
}
expectedList := &unstructured.UnstructuredList{}
expectedList.SetGroupVersionKind(listGVK)
expectedList.SetResourceVersion("") // by product of the fake setting resource version
expectedList.Items = append(expectedList.Items, u)
if diff := cmp.Diff(expectedList, list); diff != "" {
t.Fatal("unexpected diff (-want, +got): ", diff)
}
}
// This test ensures list works when the dynamic client is seeded with an empty scheme and
// unstructured typed fixtures
func TestListWithTypedFixtures(t *testing.T) {
gvr := schema.GroupVersionResource{Group: testGroup, Version: testVersion, Resource: testResource}
gvk := gvr.GroupVersion().WithKind(testKind)
listGVK := gvk
listGVK.Kind += "List"
r := mockResource{}
r.SetGroupVersionKind(gvk)
r.SetName("name")
r.SetNamespace("namespace")
u := unstructured.Unstructured{}
u.SetGroupVersionKind(r.GetObjectKind().GroupVersionKind())
u.SetName(r.GetName())
u.SetNamespace(r.GetNamespace())
// Needed see: https://github.com/kubernetes/kubernetes/issues/67610
unstructured.SetNestedField(u.Object, nil, "metadata", "creationTimestamp")
typedScheme := runtime.NewScheme()
typedScheme.AddKnownTypeWithName(gvk, &mockResource{})
typedScheme.AddKnownTypeWithName(listGVK, &mockResourceList{})
client := NewSimpleDynamicClient(typedScheme, &r)
list, err := client.Resource(gvr).Namespace("namespace").List(context.Background(), metav1.ListOptions{})
if err != nil {
t.Error("error listing", err)
}
expectedList := &unstructured.UnstructuredList{}
expectedList.SetGroupVersionKind(listGVK)
expectedList.SetResourceVersion("") // by product of the fake setting resource version
expectedList.Items = []unstructured.Unstructured{u}
if diff := cmp.Diff(expectedList, list); diff != "" {
t.Fatal("unexpected diff (-want, +got): ", diff)
}
}
type (
mockResource struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata"`
}
mockResourceList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata"`
Items []mockResource
}
)
func (l *mockResourceList) DeepCopyObject() runtime.Object {
o := *l
return &o
}
func (r *mockResource) DeepCopyObject() runtime.Object {
o := *r
return &o
}
var _ runtime.Object = (*mockResource)(nil)
var _ runtime.Object = (*mockResourceList)(nil)

8
go.mod
View File

@@ -30,8 +30,8 @@ require (
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba
google.golang.org/protobuf v1.26.0
k8s.io/api v0.0.0-20210708014407-1e1dad4bd9d1
k8s.io/apimachinery v0.0.0-20210708014216-0dafcb48b31e
k8s.io/api v0.22.0-beta.2
k8s.io/apimachinery v0.22.0-beta.2
k8s.io/klog/v2 v2.9.0
k8s.io/utils v0.0.0-20210707171843-4b05e18ac7d9
sigs.k8s.io/structured-merge-diff/v4 v4.1.2
@@ -39,6 +39,6 @@ require (
)
replace (
k8s.io/api => k8s.io/api v0.0.0-20210708014407-1e1dad4bd9d1
k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20210708014216-0dafcb48b31e
k8s.io/api => k8s.io/api v0.22.0-beta.2
k8s.io/apimachinery => k8s.io/apimachinery v0.22.0-beta.2
)

8
go.sum
View File

@@ -444,10 +444,10 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
k8s.io/api v0.0.0-20210708014407-1e1dad4bd9d1 h1:BsVbhrF9ZXWlpo/84y3WdGGw+SYu61RlNKOMqGKDIQE=
k8s.io/api v0.0.0-20210708014407-1e1dad4bd9d1/go.mod h1:UGtjmHCgj1IMXqa0rRAXNNszZBo1IKpAhbvSre33vSU=
k8s.io/apimachinery v0.0.0-20210708014216-0dafcb48b31e h1:EZ+ZrcPtnkR8vVo7bBSwoMD9a0HaC29O6lJhcZgwsJQ=
k8s.io/apimachinery v0.0.0-20210708014216-0dafcb48b31e/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0=
k8s.io/api v0.22.0-beta.2 h1:uPKPbkznLZqgCGD7GP5ij0KJr+YvmDgGlgmFZve/eWs=
k8s.io/api v0.22.0-beta.2/go.mod h1:ifj7jGqTOb5pPTmXdOtifpYvJ3MrnZJ9HPTGcEOAjCg=
k8s.io/apimachinery v0.22.0-beta.2 h1:sU0zj0wdOwFQyWssgnf3qoGCmYawlb1dYbyoL+wiWBU=
k8s.io/apimachinery v0.22.0-beta.2/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0=
k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
k8s.io/klog/v2 v2.9.0 h1:D7HV+n1V57XeZ0m6tdRkfknthUaM06VFbWldOFh8kzM=