Skip to content

Commit 8a5fa9d

Browse files
committed
Use typed client for ResourceSets
This complicates the tests a little but avoids marshalling to/from JSON.
1 parent d08b50c commit 8a5fa9d

2 files changed

Lines changed: 79 additions & 71 deletions

File tree

src/go/pkg/synk/synk.go

Lines changed: 21 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030

3131
"github.com/cenkalti/backoff"
3232
apps "github.com/googlecloudrobotics/core/src/go/pkg/apis/apps/v1alpha1"
33+
crcapi "github.com/googlecloudrobotics/core/src/go/pkg/client/versioned"
3334
"github.com/googlecloudrobotics/ilog"
3435
"github.com/pkg/errors"
3536
"go.opencensus.io/trace"
@@ -56,19 +57,24 @@ import (
5657
// src/k8s.io/apimachinery/pkg/api/validation/objectmeta.go
5758
const totalAnnotationSizeLimitB int = 256 * (1 << 10) // 256 kB
5859

60+
// Can be overridden for testing.
61+
var metav1Now = metav1.Now
62+
5963
// Synk allows to synchronize sets of resources with a fixed cluster.
6064
type Synk struct {
6165
discovery discovery.CachedDiscoveryInterface
6266
client dynamic.Interface
67+
rsClient crcapi.Interface
6368
mapper meta.RESTMapper
6469
resetMapper func()
6570
}
6671

6772
// New returns a new Synk object that acts against the cluster for the given configuration.
68-
func New(client dynamic.Interface, discovery discovery.CachedDiscoveryInterface) *Synk {
73+
func New(client dynamic.Interface, rsClient crcapi.Interface, discovery discovery.CachedDiscoveryInterface) *Synk {
6974
s := &Synk{
7075
discovery: discovery,
7176
client: client,
77+
rsClient: rsClient,
7278
}
7379
// Store reset function seperately to allow reasonable tests.
7480
m := restmapper.NewDeferredDiscoveryRESTMapper(discovery)
@@ -83,6 +89,10 @@ func NewForConfig(cfg *rest.Config) (*Synk, error) {
8389
if err != nil {
8490
return nil, err
8591
}
92+
rsClient, err := crcapi.NewForConfig(cfg)
93+
if err != nil {
94+
return nil, err
95+
}
8696
discovery, err := discovery.NewDiscoveryClientForConfig(cfg)
8797
if err != nil {
8898
return nil, err
@@ -91,7 +101,7 @@ func NewForConfig(cfg *rest.Config) (*Synk, error) {
91101
// Without initial invalidation all calls will fail.
92102
cachedDiscovery.Invalidate()
93103

94-
return New(client, cachedDiscovery), nil
104+
return New(client, rsClient, cachedDiscovery), nil
95105
}
96106

97107
// TODO: determine options that allow us to be semantically compatible with
@@ -215,7 +225,7 @@ func (s *Synk) Init() error {
215225
func (s *Synk) Delete(ctx context.Context, name string) error {
216226
policy := metav1.DeletePropagationForeground
217227
deleteOpts := metav1.DeleteOptions{PropagationPolicy: &policy}
218-
return s.client.Resource(resourceSetGVR).DeleteCollection(ctx, deleteOpts, metav1.ListOptions{
228+
return s.rsClient.AppsV1alpha1().ResourceSets().DeleteCollection(ctx, deleteOpts, metav1.ListOptions{
219229
LabelSelector: fmt.Sprintf("name=%s", name),
220230
})
221231
}
@@ -463,7 +473,7 @@ func (s *Synk) initialize(
463473

464474
rs.Status = apps.ResourceSetStatus{
465475
Phase: apps.ResourceSetPhasePending,
466-
StartedAt: metav1.Now(),
476+
StartedAt: metav1Now(),
467477
}
468478
if err := s.createResourceSet(ctx, &rs); err != nil {
469479
return nil, nil, errors.Wrapf(err, "create resources object %q", rs.Name)
@@ -854,25 +864,9 @@ func (s *Synk) crdAvailable(ucrd *unstructured.Unstructured) (bool, error) {
854864
return true, nil
855865
}
856866

857-
var resourceSetGVR = schema.GroupVersionResource{
858-
Group: "apps.cloudrobotics.com",
859-
Version: "v1alpha1",
860-
Resource: "resourcesets",
861-
}
862-
863867
func (s *Synk) createResourceSet(ctx context.Context, rs *apps.ResourceSet) error {
864-
rs.Kind = "ResourceSet"
865-
rs.APIVersion = "apps.cloudrobotics.com/v1alpha1"
866-
867-
var u unstructured.Unstructured
868-
if err := convert(rs, &u); err != nil {
869-
return err
870-
}
871-
res, err := s.client.Resource(resourceSetGVR).Create(ctx, &u, metav1.CreateOptions{})
872-
if err != nil {
873-
return err
874-
}
875-
return convert(res, rs)
868+
_, err := s.rsClient.AppsV1alpha1().ResourceSets().Create(ctx, rs, metav1.CreateOptions{})
869+
return err
876870
}
877871

878872
type applyResult struct {
@@ -951,27 +945,20 @@ func (s *Synk) updateResourceSetStatus(ctx context.Context, rs *apps.ResourceSet
951945
build(applied, &rs.Status.Applied)
952946
build(failed, &rs.Status.Failed)
953947

954-
rs.Status.FinishedAt = metav1.Now()
948+
rs.Status.FinishedAt = metav1Now()
955949
if len(rs.Status.Failed) > 0 {
956950
rs.Status.Phase = apps.ResourceSetPhaseFailed
957951
} else {
958952
rs.Status.Phase = apps.ResourceSetPhaseSettled
959953
}
960954

961-
var u unstructured.Unstructured
962-
if err := convert(rs, &u); err != nil {
963-
return err
964-
}
965-
res, err := s.client.Resource(resourceSetGVR).Update(ctx, &u, metav1.UpdateOptions{})
966-
if err != nil {
967-
return errors.Wrap(err, "update ResourceSet status")
968-
}
969-
return convert(res, rs)
955+
_, err := s.rsClient.AppsV1alpha1().ResourceSets().Update(ctx, rs, metav1.UpdateOptions{})
956+
return err
970957
}
971958

972959
// deleteResourceSets deletes all ResourceSets of the given name that have a lower version.
973960
func (s *Synk) deleteResourceSets(ctx context.Context, name string, version int32) error {
974-
c := s.client.Resource(resourceSetGVR)
961+
c := s.rsClient.AppsV1alpha1().ResourceSets()
975962

976963
list, err := c.List(ctx, metav1.ListOptions{})
977964
if err != nil {
@@ -995,7 +982,7 @@ func (s *Synk) deleteResourceSets(ctx context.Context, name string, version int3
995982

996983
// next returns the next version for the resources name.
997984
func (s *Synk) next(ctx context.Context, name string) (version int32, err error) {
998-
list, err := s.client.Resource(resourceSetGVR).List(ctx, metav1.ListOptions{})
985+
list, err := s.rsClient.AppsV1alpha1().ResourceSets().List(ctx, metav1.ListOptions{})
999986
if err != nil {
1000987
return 0, errors.Wrap(err, "list existing ResourceSets")
1001988
}

src/go/pkg/synk/synk_test.go

Lines changed: 58 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@ import (
2020
"reflect"
2121
"strings"
2222
"testing"
23+
"time"
2324

2425
apps "github.com/googlecloudrobotics/core/src/go/pkg/apis/apps/v1alpha1"
26+
crcfake "github.com/googlecloudrobotics/core/src/go/pkg/client/versioned/fake"
2527
"github.com/pkg/errors"
2628
corev1 "k8s.io/api/core/v1"
2729
k8serrors "k8s.io/apimachinery/pkg/api/errors"
@@ -59,6 +61,22 @@ func (d *fakeCachedDiscoveryClient) ServerGroupsAndResources() ([]*metav1.APIGro
5961
}, nil
6062
}
6163

64+
// Helpers to override time when testing.
65+
var (
66+
fakeEndTime = metav1.Date(2025, 01, 01, 0, 0, 0, 0, time.UTC)
67+
)
68+
69+
func fakeTime(t *testing.T, fakeTime metav1.Time) {
70+
t.Helper()
71+
oldFunc := metav1Now
72+
t.Cleanup(func() {
73+
metav1Now = oldFunc
74+
})
75+
metav1Now = func() metav1.Time {
76+
return fakeTime
77+
}
78+
}
79+
6280
type fixture struct {
6381
*testing.T
6482
fake *k8stest.Fake
@@ -78,8 +96,9 @@ func (f *fixture) newSynk() *Synk {
7896
scheme.AddToScheme(sc)
7997
apps.AddToScheme(sc) // For tests with CRDs.
8098
var (
81-
client = dynamicfake.NewSimpleDynamicClient(sc, f.objects...)
82-
s = New(client, &fakeCachedDiscoveryClient{})
99+
client = dynamicfake.NewSimpleDynamicClient(sc, f.objects...)
100+
rsClient = crcfake.NewSimpleClientset()
101+
s = New(client, rsClient, &fakeCachedDiscoveryClient{})
83102
)
84103
s.mapper = testrestmapper.TestOnlyStaticRESTMapper(sc)
85104
s.resetMapper = func() {}
@@ -167,11 +186,11 @@ func TestSynk_initialize(t *testing.T) {
167186
if err != nil {
168187
t.Fatal(err)
169188
}
170-
got, err := s.client.Resource(resourceSetGVR).Get(ctx, "test.v1", metav1.GetOptions{})
189+
got, err := s.rsClient.AppsV1alpha1().ResourceSets().Get(ctx, "test.v1", metav1.GetOptions{})
171190
if err != nil {
172191
t.Fatal(err)
173192
}
174-
var want unstructured.Unstructured
193+
var want apps.ResourceSet
175194
unmarshalYAML(t, &want, `
176195
apiVersion: apps.cloudrobotics.com/v1alpha1
177196
kind: ResourceSet
@@ -200,13 +219,11 @@ status:
200219
if want.GetName() != got.GetName() {
201220
t.Errorf("expected name %q but got %q", want.GetName(), got.GetName())
202221
}
203-
wantPhase, _, _ := unstructured.NestedString(want.Object, "status", "phase")
204-
gotPhase, _, _ := unstructured.NestedString(got.Object, "status", "phase")
205-
if wantPhase != gotPhase {
206-
t.Errorf("expected status phase %q but got %q", wantPhase, gotPhase)
222+
if want.Status.Phase != got.Status.Phase {
223+
t.Errorf("expected status phase %q but got %q", want.Status.Phase, got.Status.Phase)
207224
}
208-
if !reflect.DeepEqual(want.Object["spec"], got.Object["spec"]) {
209-
t.Errorf("expected spec\n%v\nbut got\n%v", want.Object["spec"], got.Object["spec"])
225+
if !reflect.DeepEqual(want.Spec, got.Spec) {
226+
t.Errorf("expected spec\n%v\nbut got\n%v", want.Spec, got.Spec)
210227
}
211228
}
212229

@@ -240,15 +257,16 @@ func TestSynk_updateResourceSetStatus(t *testing.T) {
240257
action: apps.ResourceActionCreate,
241258
},
242259
}
260+
fakeTime(t, fakeEndTime)
243261
err := s.updateResourceSetStatus(ctx, rs, results)
244262
if err != nil {
245263
t.Fatal(err)
246264
}
247-
got, err := s.client.Resource(resourceSetGVR).Get(ctx, "set1", metav1.GetOptions{})
265+
got, err := s.rsClient.AppsV1alpha1().ResourceSets().Get(ctx, "set1", metav1.GetOptions{})
248266
if err != nil {
249267
t.Fatal(err)
250268
}
251-
var want unstructured.Unstructured
269+
var want apps.ResourceSet
252270
unmarshalYAML(t, &want, `
253271
apiVersion: apps.cloudrobotics.com/v1alpha1
254272
kind: ResourceSet
@@ -283,15 +301,10 @@ status:
283301
name: deploy1
284302
action: Create
285303
`)
286-
if v, _, _ := unstructured.NestedString(got.Object, "status", "finishedAt"); v == "" {
287-
t.Errorf("finishedAt timestamp was not set")
288-
}
289-
// Remove unknown timestamps before running DeepEqual.
290-
unstructured.RemoveNestedField(got.Object, "status", "startedAt")
291-
unstructured.RemoveNestedField(got.Object, "status", "finishedAt")
304+
want.Status.FinishedAt = fakeEndTime
292305

293-
if !reflect.DeepEqual(got.Object["status"], want.Object["status"]) {
294-
t.Errorf("expected status:\n%q\nbut got:\n%q", want.Object["status"], got.Object["status"])
306+
if !reflect.DeepEqual(got.Status, want.Status) {
307+
t.Errorf("expected status:\n%q\nbut got:\n%q", want.Status, got.Status)
295308
}
296309
}
297310

@@ -502,11 +515,11 @@ func TestSynk_skipsTestResources(t *testing.T) {
502515
if err != nil {
503516
t.Fatal(err)
504517
}
505-
got, err := s.client.Resource(resourceSetGVR).Get(ctx, "test.v1", metav1.GetOptions{})
518+
got, err := s.rsClient.AppsV1alpha1().ResourceSets().Get(ctx, "test.v1", metav1.GetOptions{})
506519
if err != nil {
507520
t.Fatal(err)
508521
}
509-
var want unstructured.Unstructured
522+
var want apps.ResourceSet
510523
unmarshalYAML(t, &want, `
511524
apiVersion: apps.cloudrobotics.com/v1alpha1
512525
kind: ResourceSet
@@ -524,33 +537,41 @@ spec:
524537
status:
525538
phase: Pending
526539
`)
527-
if !reflect.DeepEqual(want.Object["spec"], got.Object["spec"]) {
528-
t.Errorf("expected spec\n%v\nbut got\n%v", want.Object["spec"], got.Object["spec"])
540+
if !reflect.DeepEqual(want.Spec, got.Spec) {
541+
t.Errorf("expected spec\n%v\nbut got\n%v", want.Spec, got.Spec)
529542
}
530543
}
531544

532545
func TestSynk_deleteResourceSets(t *testing.T) {
546+
initialNames := []string{"test.v2", "bad_name", "other.v3", "test.v4", "test.v7", "test.v8"}
547+
wantNames := []string{"bad_name", "other.v3", "test.v7", "test.v8"}
548+
wantDeletions := []string{"test.v2", "test.v4"}
549+
533550
ctx := context.Background()
534551
f := newFixture(t)
535-
f.addObjects(
536-
newUnstructured("apps.cloudrobotics.com/v1alpha1", "ResourceSet", "", "test.v2"),
537-
newUnstructured("apps.cloudrobotics.com/v1alpha1", "ResourceSet", "", "bad_name"),
538-
newUnstructured("apps.cloudrobotics.com/v1alpha1", "ResourceSet", "", "other.v3"),
539-
newUnstructured("apps.cloudrobotics.com/v1alpha1", "ResourceSet", "", "test.v4"),
540-
newUnstructured("apps.cloudrobotics.com/v1alpha1", "ResourceSet", "", "test.v7"),
541-
newUnstructured("apps.cloudrobotics.com/v1alpha1", "ResourceSet", "", "test.v8"),
542-
)
543552
synk := f.newSynk()
553+
rsClient := synk.rsClient.AppsV1alpha1().ResourceSets()
554+
555+
for _, name := range initialNames {
556+
rsClient.Create(ctx, &apps.ResourceSet{
557+
ObjectMeta: metav1.ObjectMeta{Name: name},
558+
}, metav1.CreateOptions{})
559+
}
544560

545561
err := synk.deleteResourceSets(ctx, "test", 7)
546562
if err != nil {
547563
t.Fatal(err)
548564
}
549-
f.expectActions(
550-
k8stest.NewRootDeleteAction(resourceSetGVR, "test.v2"),
551-
k8stest.NewRootDeleteAction(resourceSetGVR, "test.v4"),
552-
)
553-
f.verifyWriteActions()
565+
for _, name := range wantNames {
566+
if _, err := rsClient.Get(ctx, name, metav1.GetOptions{}); err != nil {
567+
t.Errorf("unexpected error getting ResourceSet %q: err=%v; want nil error", err, err)
568+
}
569+
}
570+
for _, name := range wantDeletions {
571+
if _, err := rsClient.Get(ctx, name, metav1.GetOptions{}); !k8serrors.IsNotFound(err) {
572+
t.Errorf("unexpected error getting ResourceSet %q: err=%v; want not found", name, err)
573+
}
574+
}
554575
}
555576

556577
func TestSynk_populateNamespaces(t *testing.T) {

0 commit comments

Comments
 (0)