generated from cybozu-go/neco-template
-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore: make v2alpha1/SubNamespace API kstatus compatible
- Loading branch information
Showing
13 changed files
with
495 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package v1 | ||
|
||
import ( | ||
"testing" | ||
|
||
accuratev2alpha1 "github.com/cybozu-go/accurate/api/v2alpha1" | ||
utilconversion "github.com/cybozu-go/accurate/internal/util/conversion" | ||
fuzz "github.com/google/gofuzz" | ||
"k8s.io/apimachinery/pkg/api/apitesting/fuzzer" | ||
runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer" | ||
) | ||
|
||
func TestFuzzyConversion(t *testing.T) { | ||
t.Run("for SubNamespace", utilconversion.FuzzTestFunc(utilconversion.FuzzTestFuncInput{ | ||
Hub: &accuratev2alpha1.SubNamespace{}, | ||
Spoke: &SubNamespace{}, | ||
FuzzerFuncs: []fuzzer.FuzzerFuncs{SubNamespaceStatusFuzzFunc}, | ||
})) | ||
} | ||
|
||
func SubNamespaceStatusFuzzFunc(_ runtimeserializer.CodecFactory) []interface{} { | ||
return []interface{}{ | ||
SubNamespaceStatusFuzzer, | ||
} | ||
} | ||
|
||
func SubNamespaceStatusFuzzer(in *SubNamespace, c fuzz.Continue) { | ||
c.FuzzNoCustom(in) | ||
|
||
// The status is just a string in v1, and the controller is the sole actor updating status. | ||
// As long as we make the controller reconcile v2alpha1, and also makes it the stored version, | ||
// we will never need to convert status from v1 to v2alpha1. | ||
in.Status = "" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
package v1 | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"strconv" | ||
|
||
accuratev2alpha1 "github.com/cybozu-go/accurate/api/v2alpha1" | ||
"github.com/cybozu-go/accurate/pkg/constants" | ||
"github.com/go-logr/logr" | ||
"k8s.io/apimachinery/pkg/api/meta" | ||
kstatus "sigs.k8s.io/cli-utils/pkg/kstatus/status" | ||
ctrl "sigs.k8s.io/controller-runtime" | ||
"sigs.k8s.io/controller-runtime/pkg/client" | ||
"sigs.k8s.io/controller-runtime/pkg/conversion" | ||
) | ||
|
||
// ConvertTo converts this SubNamespace to the Hub version (v2alpha1). | ||
func (src *SubNamespace) ConvertTo(dstRaw conversion.Hub) error { | ||
dst := dstRaw.(*accuratev2alpha1.SubNamespace) | ||
|
||
logger := getConversionLogger(src).WithValues( | ||
"source", GroupVersion.Version, | ||
"destination", GroupVersion.Version, | ||
) | ||
logger.V(5).Info("converting") | ||
|
||
dst.ObjectMeta = src.ObjectMeta | ||
dst.Spec.Annotations = src.Spec.Annotations | ||
dst.Spec.Labels = src.Spec.Labels | ||
|
||
// Restore info from annotations to ensure conversions are lossy-less. | ||
// Delete annotation after processing it to avoid polluting converted resource. | ||
if v, ok := dst.Annotations[constants.AnnObservedGeneration]; ok { | ||
obsGen, err := strconv.ParseInt(v, 10, 64) | ||
if err != nil { | ||
return fmt.Errorf("error converting %q to int64 from annotation %s", v, constants.AnnObservedGeneration) | ||
} | ||
dst.Status.ObservedGeneration = obsGen | ||
|
||
delete(dst.Annotations, constants.AnnObservedGeneration) | ||
} | ||
if conds, ok := dst.Annotations[constants.AnnConditions]; ok { | ||
err := json.Unmarshal([]byte(conds), &dst.Status.Conditions) | ||
if err != nil { | ||
return fmt.Errorf("error unmarshalling JSON from annotation %s", constants.AnnConditions) | ||
} | ||
|
||
delete(dst.Annotations, constants.AnnConditions) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// ConvertFrom converts from the Hub version (v2alpha1) to this version. | ||
func (dst *SubNamespace) ConvertFrom(srcRaw conversion.Hub) error { | ||
src := srcRaw.(*accuratev2alpha1.SubNamespace) | ||
|
||
logger := getConversionLogger(src).WithValues( | ||
"source", GroupVersion.Version, | ||
"destination", GroupVersion.Version, | ||
) | ||
logger.V(5).Info("converting") | ||
|
||
dst.ObjectMeta = src.ObjectMeta | ||
dst.Spec.Annotations = src.Spec.Annotations | ||
dst.Spec.Labels = src.Spec.Labels | ||
|
||
switch { | ||
case meta.IsStatusConditionTrue(src.Status.Conditions, string(kstatus.ConditionStalled)): | ||
dst.Status = SubNamespaceConflict | ||
case src.Status.ObservedGeneration == 0: | ||
// SubNamespace has never been reconciled. | ||
case src.Status.ObservedGeneration == src.Generation && len(src.Status.Conditions) == 0: | ||
dst.Status = SubNamespaceOK | ||
default: | ||
// SubNamespace is in some transitional state, not possible to represent in v1 status. | ||
// An unset value is probably our best option. | ||
} | ||
|
||
// Store info in annotations to ensure conversions are lossy-less. | ||
if dst.Annotations == nil { | ||
dst.Annotations = make(map[string]string) | ||
} | ||
if src.Status.ObservedGeneration != 0 { | ||
dst.Annotations[constants.AnnObservedGeneration] = strconv.FormatInt(src.Status.ObservedGeneration, 10) | ||
} | ||
if len(src.Status.Conditions) > 0 { | ||
buf, err := json.Marshal(src.Status.Conditions) | ||
if err != nil { | ||
return fmt.Errorf("error marshalling conditions to JSON") | ||
} | ||
dst.Annotations[constants.AnnConditions] = string(buf) | ||
} | ||
if len(dst.Annotations) == 0 { | ||
dst.Annotations = nil | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func getConversionLogger(obj client.Object) logr.Logger { | ||
return ctrl.Log.WithName("conversion").WithValues( | ||
"kind", "SubNamespace", | ||
"namespace", obj.GetNamespace(), | ||
"name", obj.GetName(), | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
package v1 | ||
|
||
import ( | ||
accuratev2alpha1 "github.com/cybozu-go/accurate/api/v2alpha1" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
kstatus "sigs.k8s.io/cli-utils/pkg/kstatus/status" | ||
"testing" | ||
) | ||
|
||
func TestSubNamespace_ConvertFrom(t *testing.T) { | ||
tests := map[string]struct { | ||
src *accuratev2alpha1.SubNamespace | ||
expStatus SubNamespaceStatus | ||
wantErr bool | ||
}{ | ||
"if SubNamespace has never been reconciled, status should have zero-value": { | ||
src: newSubNamespaceWithStatus(0, 0), | ||
}, | ||
"if SubNamespace spec is updated, but not yet reconciled, status should have zero-value": { | ||
src: newSubNamespaceWithStatus(2, 1), | ||
}, | ||
"if SubNamespace is reconciled successfully, status should be ok": { | ||
src: newSubNamespaceWithStatus(1, 1), | ||
expStatus: SubNamespaceOK, | ||
}, | ||
"if SubNamespace is reconciled with errors, status should be conflict": { | ||
src: newSubNamespaceWithStatus(1, 1, newStalledCondition()), | ||
expStatus: SubNamespaceConflict, | ||
}, | ||
} | ||
for n, tt := range tests { | ||
t.Run(n, func(t *testing.T) { | ||
dst := &SubNamespace{} | ||
if err := dst.ConvertFrom(tt.src); (err != nil) != tt.wantErr { | ||
t.Errorf("ConvertFrom() error = %v, wantErr %v", err, tt.wantErr) | ||
} | ||
if dst.Status != tt.expStatus { | ||
t.Errorf("ConvertFrom() status = %q, expStatus %q", dst.Status, tt.expStatus) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func newSubNamespaceWithStatus(gen, obsGen int, conds ...metav1.Condition) *accuratev2alpha1.SubNamespace { | ||
subNS := &accuratev2alpha1.SubNamespace{} | ||
subNS.Generation = int64(gen) | ||
subNS.Status.ObservedGeneration = int64(obsGen) | ||
subNS.Status.Conditions = conds | ||
return subNS | ||
} | ||
|
||
func newStalledCondition() metav1.Condition { | ||
return metav1.Condition{ | ||
Type: string(kstatus.ConditionStalled), | ||
Status: metav1.ConditionTrue, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
package v2alpha1 | ||
|
||
// Hub marks this SubNamespace version as a conversion hub. | ||
func (*SubNamespace) Hub() {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
59 changes: 55 additions & 4 deletions
59
charts/accurate/crds/accurate.cybozu.com_subnamespaces.yaml
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.