Provide handler for LokiStack create and update events (#27)

pull/4881/head
Periklis Tsirakidis 5 years ago committed by GitHub
parent 0949632dca
commit 03ce63b730
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 11
      controllers/lokistack_controller.go
  2. 13
      internal/external/k8s/client.go
  3. 589
      internal/external/k8s/k8sfakes/fake_client.go
  4. 54
      internal/handlers/lokistack_create_or_update.go
  5. 440
      internal/handlers/lokistack_create_or_update_test.go
  6. 163
      internal/handlers/lokistack_create_test.go
  7. 84
      internal/manifests/mutate.go
  8. 410
      internal/manifests/mutate_test.go
  9. 1
      internal/manifests/querier.go
  10. 1
      internal/manifests/query-frontend.go

@ -54,7 +54,7 @@ type LokiStackReconciler struct {
// For more details, check Reconcile and its Result here:
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.7.0/pkg/reconcile
func (r *LokiStackReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
err := handlers.CreateLokiStack(ctx, req, r.Client)
err := handlers.CreateOrUpdateLokiStack(ctx, req, r.Client, r.Scheme)
if err != nil {
return ctrl.Result{
Requeue: true,
@ -67,8 +67,11 @@ func (r *LokiStackReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
// SetupWithManager sets up the controller with the Manager.
func (r *LokiStackReconciler) SetupWithManager(mgr ctrl.Manager) error {
createPredicate := predicate.Funcs{
UpdateFunc: func(e event.UpdateEvent) bool { return false },
filter := predicate.Funcs{
UpdateFunc: func(e event.UpdateEvent) bool {
// Update only if generation changes, filter out anything else
return e.ObjectOld.GetGeneration() != e.ObjectNew.GetGeneration()
},
CreateFunc: func(e event.CreateEvent) bool { return true },
DeleteFunc: func(e event.DeleteEvent) bool { return false },
GenericFunc: func(e event.GenericEvent) bool { return false },
@ -76,6 +79,6 @@ func (r *LokiStackReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&lokiv1beta1.LokiStack{}).
WithEventFilter(createPredicate).
WithEventFilter(filter).
Complete(r)
}

@ -3,6 +3,9 @@ package k8s
import (
"context"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
)
@ -13,4 +16,14 @@ import (
type Client interface {
Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error
Get(ctx context.Context, key client.ObjectKey, obj client.Object) error
Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error
Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error
DeleteAllOf(ctx context.Context, obj client.Object, opts ...client.DeleteAllOfOption) error
List(ctx context.Context, obj client.ObjectList, opts ...client.ListOption) error
Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error
Status() client.StatusWriter
RESTMapper() meta.RESTMapper
Scheme() *runtime.Scheme
}

@ -6,6 +6,8 @@ import (
"sync"
"github.com/ViaQ/loki-operator/internal/external/k8s"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
)
@ -24,6 +26,32 @@ type FakeClient struct {
createReturnsOnCall map[int]struct {
result1 error
}
DeleteStub func(context.Context, client.Object, ...client.DeleteOption) error
deleteMutex sync.RWMutex
deleteArgsForCall []struct {
arg1 context.Context
arg2 client.Object
arg3 []client.DeleteOption
}
deleteReturns struct {
result1 error
}
deleteReturnsOnCall map[int]struct {
result1 error
}
DeleteAllOfStub func(context.Context, client.Object, ...client.DeleteAllOfOption) error
deleteAllOfMutex sync.RWMutex
deleteAllOfArgsForCall []struct {
arg1 context.Context
arg2 client.Object
arg3 []client.DeleteAllOfOption
}
deleteAllOfReturns struct {
result1 error
}
deleteAllOfReturnsOnCall map[int]struct {
result1 error
}
GetStub func(context.Context, types.NamespacedName, client.Object) error
getMutex sync.RWMutex
getArgsForCall []struct {
@ -37,6 +65,76 @@ type FakeClient struct {
getReturnsOnCall map[int]struct {
result1 error
}
ListStub func(context.Context, client.ObjectList, ...client.ListOption) error
listMutex sync.RWMutex
listArgsForCall []struct {
arg1 context.Context
arg2 client.ObjectList
arg3 []client.ListOption
}
listReturns struct {
result1 error
}
listReturnsOnCall map[int]struct {
result1 error
}
PatchStub func(context.Context, client.Object, client.Patch, ...client.PatchOption) error
patchMutex sync.RWMutex
patchArgsForCall []struct {
arg1 context.Context
arg2 client.Object
arg3 client.Patch
arg4 []client.PatchOption
}
patchReturns struct {
result1 error
}
patchReturnsOnCall map[int]struct {
result1 error
}
RESTMapperStub func() meta.RESTMapper
rESTMapperMutex sync.RWMutex
rESTMapperArgsForCall []struct {
}
rESTMapperReturns struct {
result1 meta.RESTMapper
}
rESTMapperReturnsOnCall map[int]struct {
result1 meta.RESTMapper
}
SchemeStub func() *runtime.Scheme
schemeMutex sync.RWMutex
schemeArgsForCall []struct {
}
schemeReturns struct {
result1 *runtime.Scheme
}
schemeReturnsOnCall map[int]struct {
result1 *runtime.Scheme
}
StatusStub func() client.StatusWriter
statusMutex sync.RWMutex
statusArgsForCall []struct {
}
statusReturns struct {
result1 client.StatusWriter
}
statusReturnsOnCall map[int]struct {
result1 client.StatusWriter
}
UpdateStub func(context.Context, client.Object, ...client.UpdateOption) error
updateMutex sync.RWMutex
updateArgsForCall []struct {
arg1 context.Context
arg2 client.Object
arg3 []client.UpdateOption
}
updateReturns struct {
result1 error
}
updateReturnsOnCall map[int]struct {
result1 error
}
invocations map[string][][]interface{}
invocationsMutex sync.RWMutex
}
@ -104,6 +202,132 @@ func (fake *FakeClient) CreateReturnsOnCall(i int, result1 error) {
}{result1}
}
func (fake *FakeClient) Delete(arg1 context.Context, arg2 client.Object, arg3 ...client.DeleteOption) error {
fake.deleteMutex.Lock()
ret, specificReturn := fake.deleteReturnsOnCall[len(fake.deleteArgsForCall)]
fake.deleteArgsForCall = append(fake.deleteArgsForCall, struct {
arg1 context.Context
arg2 client.Object
arg3 []client.DeleteOption
}{arg1, arg2, arg3})
stub := fake.DeleteStub
fakeReturns := fake.deleteReturns
fake.recordInvocation("Delete", []interface{}{arg1, arg2, arg3})
fake.deleteMutex.Unlock()
if stub != nil {
return stub(arg1, arg2, arg3...)
}
if specificReturn {
return ret.result1
}
return fakeReturns.result1
}
func (fake *FakeClient) DeleteCallCount() int {
fake.deleteMutex.RLock()
defer fake.deleteMutex.RUnlock()
return len(fake.deleteArgsForCall)
}
func (fake *FakeClient) DeleteCalls(stub func(context.Context, client.Object, ...client.DeleteOption) error) {
fake.deleteMutex.Lock()
defer fake.deleteMutex.Unlock()
fake.DeleteStub = stub
}
func (fake *FakeClient) DeleteArgsForCall(i int) (context.Context, client.Object, []client.DeleteOption) {
fake.deleteMutex.RLock()
defer fake.deleteMutex.RUnlock()
argsForCall := fake.deleteArgsForCall[i]
return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3
}
func (fake *FakeClient) DeleteReturns(result1 error) {
fake.deleteMutex.Lock()
defer fake.deleteMutex.Unlock()
fake.DeleteStub = nil
fake.deleteReturns = struct {
result1 error
}{result1}
}
func (fake *FakeClient) DeleteReturnsOnCall(i int, result1 error) {
fake.deleteMutex.Lock()
defer fake.deleteMutex.Unlock()
fake.DeleteStub = nil
if fake.deleteReturnsOnCall == nil {
fake.deleteReturnsOnCall = make(map[int]struct {
result1 error
})
}
fake.deleteReturnsOnCall[i] = struct {
result1 error
}{result1}
}
func (fake *FakeClient) DeleteAllOf(arg1 context.Context, arg2 client.Object, arg3 ...client.DeleteAllOfOption) error {
fake.deleteAllOfMutex.Lock()
ret, specificReturn := fake.deleteAllOfReturnsOnCall[len(fake.deleteAllOfArgsForCall)]
fake.deleteAllOfArgsForCall = append(fake.deleteAllOfArgsForCall, struct {
arg1 context.Context
arg2 client.Object
arg3 []client.DeleteAllOfOption
}{arg1, arg2, arg3})
stub := fake.DeleteAllOfStub
fakeReturns := fake.deleteAllOfReturns
fake.recordInvocation("DeleteAllOf", []interface{}{arg1, arg2, arg3})
fake.deleteAllOfMutex.Unlock()
if stub != nil {
return stub(arg1, arg2, arg3...)
}
if specificReturn {
return ret.result1
}
return fakeReturns.result1
}
func (fake *FakeClient) DeleteAllOfCallCount() int {
fake.deleteAllOfMutex.RLock()
defer fake.deleteAllOfMutex.RUnlock()
return len(fake.deleteAllOfArgsForCall)
}
func (fake *FakeClient) DeleteAllOfCalls(stub func(context.Context, client.Object, ...client.DeleteAllOfOption) error) {
fake.deleteAllOfMutex.Lock()
defer fake.deleteAllOfMutex.Unlock()
fake.DeleteAllOfStub = stub
}
func (fake *FakeClient) DeleteAllOfArgsForCall(i int) (context.Context, client.Object, []client.DeleteAllOfOption) {
fake.deleteAllOfMutex.RLock()
defer fake.deleteAllOfMutex.RUnlock()
argsForCall := fake.deleteAllOfArgsForCall[i]
return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3
}
func (fake *FakeClient) DeleteAllOfReturns(result1 error) {
fake.deleteAllOfMutex.Lock()
defer fake.deleteAllOfMutex.Unlock()
fake.DeleteAllOfStub = nil
fake.deleteAllOfReturns = struct {
result1 error
}{result1}
}
func (fake *FakeClient) DeleteAllOfReturnsOnCall(i int, result1 error) {
fake.deleteAllOfMutex.Lock()
defer fake.deleteAllOfMutex.Unlock()
fake.DeleteAllOfStub = nil
if fake.deleteAllOfReturnsOnCall == nil {
fake.deleteAllOfReturnsOnCall = make(map[int]struct {
result1 error
})
}
fake.deleteAllOfReturnsOnCall[i] = struct {
result1 error
}{result1}
}
func (fake *FakeClient) Get(arg1 context.Context, arg2 types.NamespacedName, arg3 client.Object) error {
fake.getMutex.Lock()
ret, specificReturn := fake.getReturnsOnCall[len(fake.getArgsForCall)]
@ -167,13 +391,378 @@ func (fake *FakeClient) GetReturnsOnCall(i int, result1 error) {
}{result1}
}
func (fake *FakeClient) List(arg1 context.Context, arg2 client.ObjectList, arg3 ...client.ListOption) error {
fake.listMutex.Lock()
ret, specificReturn := fake.listReturnsOnCall[len(fake.listArgsForCall)]
fake.listArgsForCall = append(fake.listArgsForCall, struct {
arg1 context.Context
arg2 client.ObjectList
arg3 []client.ListOption
}{arg1, arg2, arg3})
stub := fake.ListStub
fakeReturns := fake.listReturns
fake.recordInvocation("List", []interface{}{arg1, arg2, arg3})
fake.listMutex.Unlock()
if stub != nil {
return stub(arg1, arg2, arg3...)
}
if specificReturn {
return ret.result1
}
return fakeReturns.result1
}
func (fake *FakeClient) ListCallCount() int {
fake.listMutex.RLock()
defer fake.listMutex.RUnlock()
return len(fake.listArgsForCall)
}
func (fake *FakeClient) ListCalls(stub func(context.Context, client.ObjectList, ...client.ListOption) error) {
fake.listMutex.Lock()
defer fake.listMutex.Unlock()
fake.ListStub = stub
}
func (fake *FakeClient) ListArgsForCall(i int) (context.Context, client.ObjectList, []client.ListOption) {
fake.listMutex.RLock()
defer fake.listMutex.RUnlock()
argsForCall := fake.listArgsForCall[i]
return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3
}
func (fake *FakeClient) ListReturns(result1 error) {
fake.listMutex.Lock()
defer fake.listMutex.Unlock()
fake.ListStub = nil
fake.listReturns = struct {
result1 error
}{result1}
}
func (fake *FakeClient) ListReturnsOnCall(i int, result1 error) {
fake.listMutex.Lock()
defer fake.listMutex.Unlock()
fake.ListStub = nil
if fake.listReturnsOnCall == nil {
fake.listReturnsOnCall = make(map[int]struct {
result1 error
})
}
fake.listReturnsOnCall[i] = struct {
result1 error
}{result1}
}
func (fake *FakeClient) Patch(arg1 context.Context, arg2 client.Object, arg3 client.Patch, arg4 ...client.PatchOption) error {
fake.patchMutex.Lock()
ret, specificReturn := fake.patchReturnsOnCall[len(fake.patchArgsForCall)]
fake.patchArgsForCall = append(fake.patchArgsForCall, struct {
arg1 context.Context
arg2 client.Object
arg3 client.Patch
arg4 []client.PatchOption
}{arg1, arg2, arg3, arg4})
stub := fake.PatchStub
fakeReturns := fake.patchReturns
fake.recordInvocation("Patch", []interface{}{arg1, arg2, arg3, arg4})
fake.patchMutex.Unlock()
if stub != nil {
return stub(arg1, arg2, arg3, arg4...)
}
if specificReturn {
return ret.result1
}
return fakeReturns.result1
}
func (fake *FakeClient) PatchCallCount() int {
fake.patchMutex.RLock()
defer fake.patchMutex.RUnlock()
return len(fake.patchArgsForCall)
}
func (fake *FakeClient) PatchCalls(stub func(context.Context, client.Object, client.Patch, ...client.PatchOption) error) {
fake.patchMutex.Lock()
defer fake.patchMutex.Unlock()
fake.PatchStub = stub
}
func (fake *FakeClient) PatchArgsForCall(i int) (context.Context, client.Object, client.Patch, []client.PatchOption) {
fake.patchMutex.RLock()
defer fake.patchMutex.RUnlock()
argsForCall := fake.patchArgsForCall[i]
return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4
}
func (fake *FakeClient) PatchReturns(result1 error) {
fake.patchMutex.Lock()
defer fake.patchMutex.Unlock()
fake.PatchStub = nil
fake.patchReturns = struct {
result1 error
}{result1}
}
func (fake *FakeClient) PatchReturnsOnCall(i int, result1 error) {
fake.patchMutex.Lock()
defer fake.patchMutex.Unlock()
fake.PatchStub = nil
if fake.patchReturnsOnCall == nil {
fake.patchReturnsOnCall = make(map[int]struct {
result1 error
})
}
fake.patchReturnsOnCall[i] = struct {
result1 error
}{result1}
}
func (fake *FakeClient) RESTMapper() meta.RESTMapper {
fake.rESTMapperMutex.Lock()
ret, specificReturn := fake.rESTMapperReturnsOnCall[len(fake.rESTMapperArgsForCall)]
fake.rESTMapperArgsForCall = append(fake.rESTMapperArgsForCall, struct {
}{})
stub := fake.RESTMapperStub
fakeReturns := fake.rESTMapperReturns
fake.recordInvocation("RESTMapper", []interface{}{})
fake.rESTMapperMutex.Unlock()
if stub != nil {
return stub()
}
if specificReturn {
return ret.result1
}
return fakeReturns.result1
}
func (fake *FakeClient) RESTMapperCallCount() int {
fake.rESTMapperMutex.RLock()
defer fake.rESTMapperMutex.RUnlock()
return len(fake.rESTMapperArgsForCall)
}
func (fake *FakeClient) RESTMapperCalls(stub func() meta.RESTMapper) {
fake.rESTMapperMutex.Lock()
defer fake.rESTMapperMutex.Unlock()
fake.RESTMapperStub = stub
}
func (fake *FakeClient) RESTMapperReturns(result1 meta.RESTMapper) {
fake.rESTMapperMutex.Lock()
defer fake.rESTMapperMutex.Unlock()
fake.RESTMapperStub = nil
fake.rESTMapperReturns = struct {
result1 meta.RESTMapper
}{result1}
}
func (fake *FakeClient) RESTMapperReturnsOnCall(i int, result1 meta.RESTMapper) {
fake.rESTMapperMutex.Lock()
defer fake.rESTMapperMutex.Unlock()
fake.RESTMapperStub = nil
if fake.rESTMapperReturnsOnCall == nil {
fake.rESTMapperReturnsOnCall = make(map[int]struct {
result1 meta.RESTMapper
})
}
fake.rESTMapperReturnsOnCall[i] = struct {
result1 meta.RESTMapper
}{result1}
}
func (fake *FakeClient) Scheme() *runtime.Scheme {
fake.schemeMutex.Lock()
ret, specificReturn := fake.schemeReturnsOnCall[len(fake.schemeArgsForCall)]
fake.schemeArgsForCall = append(fake.schemeArgsForCall, struct {
}{})
stub := fake.SchemeStub
fakeReturns := fake.schemeReturns
fake.recordInvocation("Scheme", []interface{}{})
fake.schemeMutex.Unlock()
if stub != nil {
return stub()
}
if specificReturn {
return ret.result1
}
return fakeReturns.result1
}
func (fake *FakeClient) SchemeCallCount() int {
fake.schemeMutex.RLock()
defer fake.schemeMutex.RUnlock()
return len(fake.schemeArgsForCall)
}
func (fake *FakeClient) SchemeCalls(stub func() *runtime.Scheme) {
fake.schemeMutex.Lock()
defer fake.schemeMutex.Unlock()
fake.SchemeStub = stub
}
func (fake *FakeClient) SchemeReturns(result1 *runtime.Scheme) {
fake.schemeMutex.Lock()
defer fake.schemeMutex.Unlock()
fake.SchemeStub = nil
fake.schemeReturns = struct {
result1 *runtime.Scheme
}{result1}
}
func (fake *FakeClient) SchemeReturnsOnCall(i int, result1 *runtime.Scheme) {
fake.schemeMutex.Lock()
defer fake.schemeMutex.Unlock()
fake.SchemeStub = nil
if fake.schemeReturnsOnCall == nil {
fake.schemeReturnsOnCall = make(map[int]struct {
result1 *runtime.Scheme
})
}
fake.schemeReturnsOnCall[i] = struct {
result1 *runtime.Scheme
}{result1}
}
func (fake *FakeClient) Status() client.StatusWriter {
fake.statusMutex.Lock()
ret, specificReturn := fake.statusReturnsOnCall[len(fake.statusArgsForCall)]
fake.statusArgsForCall = append(fake.statusArgsForCall, struct {
}{})
stub := fake.StatusStub
fakeReturns := fake.statusReturns
fake.recordInvocation("Status", []interface{}{})
fake.statusMutex.Unlock()
if stub != nil {
return stub()
}
if specificReturn {
return ret.result1
}
return fakeReturns.result1
}
func (fake *FakeClient) StatusCallCount() int {
fake.statusMutex.RLock()
defer fake.statusMutex.RUnlock()
return len(fake.statusArgsForCall)
}
func (fake *FakeClient) StatusCalls(stub func() client.StatusWriter) {
fake.statusMutex.Lock()
defer fake.statusMutex.Unlock()
fake.StatusStub = stub
}
func (fake *FakeClient) StatusReturns(result1 client.StatusWriter) {
fake.statusMutex.Lock()
defer fake.statusMutex.Unlock()
fake.StatusStub = nil
fake.statusReturns = struct {
result1 client.StatusWriter
}{result1}
}
func (fake *FakeClient) StatusReturnsOnCall(i int, result1 client.StatusWriter) {
fake.statusMutex.Lock()
defer fake.statusMutex.Unlock()
fake.StatusStub = nil
if fake.statusReturnsOnCall == nil {
fake.statusReturnsOnCall = make(map[int]struct {
result1 client.StatusWriter
})
}
fake.statusReturnsOnCall[i] = struct {
result1 client.StatusWriter
}{result1}
}
func (fake *FakeClient) Update(arg1 context.Context, arg2 client.Object, arg3 ...client.UpdateOption) error {
fake.updateMutex.Lock()
ret, specificReturn := fake.updateReturnsOnCall[len(fake.updateArgsForCall)]
fake.updateArgsForCall = append(fake.updateArgsForCall, struct {
arg1 context.Context
arg2 client.Object
arg3 []client.UpdateOption
}{arg1, arg2, arg3})
stub := fake.UpdateStub
fakeReturns := fake.updateReturns
fake.recordInvocation("Update", []interface{}{arg1, arg2, arg3})
fake.updateMutex.Unlock()
if stub != nil {
return stub(arg1, arg2, arg3...)
}
if specificReturn {
return ret.result1
}
return fakeReturns.result1
}
func (fake *FakeClient) UpdateCallCount() int {
fake.updateMutex.RLock()
defer fake.updateMutex.RUnlock()
return len(fake.updateArgsForCall)
}
func (fake *FakeClient) UpdateCalls(stub func(context.Context, client.Object, ...client.UpdateOption) error) {
fake.updateMutex.Lock()
defer fake.updateMutex.Unlock()
fake.UpdateStub = stub
}
func (fake *FakeClient) UpdateArgsForCall(i int) (context.Context, client.Object, []client.UpdateOption) {
fake.updateMutex.RLock()
defer fake.updateMutex.RUnlock()
argsForCall := fake.updateArgsForCall[i]
return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3
}
func (fake *FakeClient) UpdateReturns(result1 error) {
fake.updateMutex.Lock()
defer fake.updateMutex.Unlock()
fake.UpdateStub = nil
fake.updateReturns = struct {
result1 error
}{result1}
}
func (fake *FakeClient) UpdateReturnsOnCall(i int, result1 error) {
fake.updateMutex.Lock()
defer fake.updateMutex.Unlock()
fake.UpdateStub = nil
if fake.updateReturnsOnCall == nil {
fake.updateReturnsOnCall = make(map[int]struct {
result1 error
})
}
fake.updateReturnsOnCall[i] = struct {
result1 error
}{result1}
}
func (fake *FakeClient) Invocations() map[string][][]interface{} {
fake.invocationsMutex.RLock()
defer fake.invocationsMutex.RUnlock()
fake.createMutex.RLock()
defer fake.createMutex.RUnlock()
fake.deleteMutex.RLock()
defer fake.deleteMutex.RUnlock()
fake.deleteAllOfMutex.RLock()
defer fake.deleteAllOfMutex.RUnlock()
fake.getMutex.RLock()
defer fake.getMutex.RUnlock()
fake.listMutex.RLock()
defer fake.listMutex.RUnlock()
fake.patchMutex.RLock()
defer fake.patchMutex.RUnlock()
fake.rESTMapperMutex.RLock()
defer fake.rESTMapperMutex.RUnlock()
fake.schemeMutex.RLock()
defer fake.schemeMutex.RUnlock()
fake.statusMutex.RLock()
defer fake.statusMutex.RUnlock()
fake.updateMutex.RLock()
defer fake.updateMutex.RUnlock()
copiedInvocations := map[string][][]interface{}{}
for key, value := range fake.invocations {
copiedInvocations[key] = value

@ -2,23 +2,26 @@ package handlers
import (
"context"
"fmt"
"os"
"github.com/ViaQ/logerr/kverrors"
"github.com/ViaQ/logerr/log"
lokiv1beta1 "github.com/ViaQ/loki-operator/api/v1beta1"
"github.com/ViaQ/loki-operator/internal/external/k8s"
"github.com/ViaQ/loki-operator/internal/manifests"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/pointer"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
)
// CreateLokiStack handles a LokiStack create event
func CreateLokiStack(ctx context.Context, req ctrl.Request, k k8s.Client) error {
ll := log.WithValues("lokistack", req.NamespacedName, "event", "create")
// CreateOrUpdateLokiStack handles LokiStack create and update events.
func CreateOrUpdateLokiStack(ctx context.Context, req ctrl.Request, k k8s.Client, s *runtime.Scheme) error {
ll := log.WithValues("lokistack", req.NamespacedName, "event", "createOrUpdate")
var stack lokiv1beta1.LokiStack
if err := k.Get(ctx, req.NamespacedName, &stack); err != nil {
@ -51,30 +54,37 @@ func CreateLokiStack(ctx context.Context, req ctrl.Request, k k8s.Client) error
return err
}
ll.Info("manifests built", "count", len(objects))
var errCount int32
for _, obj := range objects {
l := ll.WithValues("object_name", obj.GetName(),
l := ll.WithValues(
"object_name", obj.GetName(),
"object_kind", obj.GetObjectKind(),
"object", obj)
)
obj.SetNamespace(req.Namespace)
setOwner(stack, obj)
if err := k.Create(ctx, obj); err != nil {
l.Error(err, "failed to create object")
// TODO requeue the event, but continue anyway
if err := ctrl.SetControllerReference(&stack, obj, s); err != nil {
l.Error(err, "failed to set controller owner reference to resource")
errCount++
continue
}
l.Info("Resource created", "resource", obj.GetName())
desired := obj.DeepCopyObject().(client.Object)
mutateFn := manifests.MutateFuncFor(obj, desired)
op, err := ctrl.CreateOrUpdate(ctx, k, obj, mutateFn)
if err != nil {
l.Error(err, "failed to configure resource")
errCount++
continue
}
l.Info(fmt.Sprintf("Resource has been %s", op))
}
return nil
}
if errCount > 0 {
return kverrors.New("failed to configure lokistack resources", "name", req.NamespacedName)
}
func setOwner(stack lokiv1beta1.LokiStack, o client.Object) {
o.SetOwnerReferences(append(o.GetOwnerReferences(), metav1.OwnerReference{
APIVersion: lokiv1beta1.GroupVersion.String(),
Kind: stack.Kind,
Name: stack.Name,
UID: stack.UID,
Controller: pointer.BoolPtr(true),
}))
return nil
}

@ -0,0 +1,440 @@
package handlers_test
import (
"context"
"errors"
"flag"
"io/ioutil"
"os"
"testing"
"github.com/ViaQ/logerr/log"
lokiv1beta1 "github.com/ViaQ/loki-operator/api/v1beta1"
"github.com/ViaQ/loki-operator/internal/external/k8s/k8sfakes"
"github.com/ViaQ/loki-operator/internal/handlers"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
"k8s.io/utils/pointer"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
)
var scheme = runtime.NewScheme()
func TestMain(m *testing.M) {
testing.Init()
flag.Parse()
if testing.Verbose() {
// set to the highest for verbose testing
log.SetLogLevel(5)
} else {
if err := log.SetOutput(ioutil.Discard); err != nil {
// This would only happen if the default logger was changed which it hasn't so
// we can assume that a panic is necessary and the developer is to blame.
panic(err)
}
}
// Register the clientgo and CRD schemes
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
utilruntime.Must(lokiv1beta1.AddToScheme(scheme))
log.Init("testing")
os.Exit(m.Run())
}
func TestCreateOrUpdateLokiStack_WhenGetReturnsNotFound_DoesNotError(t *testing.T) {
k := &k8sfakes.FakeClient{}
r := ctrl.Request{
NamespacedName: types.NamespacedName{
Name: "my-stack",
Namespace: "some-ns",
},
}
k.GetStub = func(ctx context.Context, name types.NamespacedName, object client.Object) error {
return apierrors.NewNotFound(schema.GroupResource{}, "something wasn't found")
}
err := handlers.CreateOrUpdateLokiStack(context.TODO(), r, k, scheme)
require.NoError(t, err)
// make sure create was NOT called because the Get failed
require.Zero(t, k.CreateCallCount())
}
func TestCreateOrUpdateLokiStack_WhenGetReturnsAnErrorOtherThanNotFound_ReturnsTheError(t *testing.T) {
k := &k8sfakes.FakeClient{}
r := ctrl.Request{
NamespacedName: types.NamespacedName{
Name: "my-stack",
Namespace: "some-ns",
},
}
badRequestErr := apierrors.NewBadRequest("you do not belong here")
k.GetStub = func(ctx context.Context, name types.NamespacedName, object client.Object) error {
return badRequestErr
}
err := handlers.CreateOrUpdateLokiStack(context.TODO(), r, k, scheme)
require.Equal(t, badRequestErr, errors.Unwrap(err))
// make sure create was NOT called because the Get failed
require.Zero(t, k.CreateCallCount())
}
func TestCreateOrUpdateLokiStack_SetsNamespaceOnAllObjects(t *testing.T) {
k := &k8sfakes.FakeClient{}
r := ctrl.Request{
NamespacedName: types.NamespacedName{
Name: "my-stack",
Namespace: "some-ns",
},
}
k.GetStub = func(_ context.Context, name types.NamespacedName, _ client.Object) error {
if r.Name == name.Name && r.Namespace == name.Namespace {
return nil
}
return apierrors.NewNotFound(schema.GroupResource{}, "something wasn't found")
}
k.CreateStub = func(_ context.Context, o client.Object, _ ...client.CreateOption) error {
assert.Equal(t, r.Namespace, o.GetNamespace())
return nil
}
err := handlers.CreateOrUpdateLokiStack(context.TODO(), r, k, scheme)
require.NoError(t, err)
// make sure create was called
require.NotZero(t, k.CreateCallCount())
}
func TestCreateOrUpdateLokiStack_SetsOwnerRefOnAllObjects(t *testing.T) {
k := &k8sfakes.FakeClient{}
r := ctrl.Request{
NamespacedName: types.NamespacedName{
Name: "my-stack",
Namespace: "some-ns",
},
}
stack := lokiv1beta1.LokiStack{
TypeMeta: metav1.TypeMeta{
Kind: "LokiStack",
},
ObjectMeta: metav1.ObjectMeta{
Name: "someStack",
Namespace: "some-ns",
UID: "b23f9a38-9672-499f-8c29-15ede74d3ece",
},
}
// Create looks up the CR first, so we need to return our fake stack
k.GetStub = func(_ context.Context, name types.NamespacedName, object client.Object) error {
if r.Name == name.Name && r.Namespace == name.Namespace {
k.SetClientObject(object, &stack)
return nil
}
return apierrors.NewNotFound(schema.GroupResource{}, "something wasn't found")
}
expected := metav1.OwnerReference{
APIVersion: lokiv1beta1.GroupVersion.String(),
Kind: stack.Kind,
Name: stack.Name,
UID: stack.UID,
Controller: pointer.BoolPtr(true),
BlockOwnerDeletion: pointer.BoolPtr(true),
}
k.CreateStub = func(_ context.Context, o client.Object, _ ...client.CreateOption) error {
// OwnerRefs are appended so we have to find ours in the list
var ref metav1.OwnerReference
var found bool
for _, or := range o.GetOwnerReferences() {
if or.UID == stack.UID {
found = true
ref = or
break
}
}
require.True(t, found, "expected to find a matching ownerRef, but did not")
require.EqualValues(t, expected, ref)
return nil
}
err := handlers.CreateOrUpdateLokiStack(context.TODO(), r, k, scheme)
require.NoError(t, err)
// make sure create was called
require.NotZero(t, k.CreateCallCount())
}
func TestCreateOrUpdateLokiStack_WhenSetControllerRefInvalid_ContinueWithOtherObjects(t *testing.T) {
k := &k8sfakes.FakeClient{}
r := ctrl.Request{
NamespacedName: types.NamespacedName{
Name: "my-stack",
Namespace: "some-ns",
},
}
stack := lokiv1beta1.LokiStack{
TypeMeta: metav1.TypeMeta{
Kind: "LokiStack",
},
ObjectMeta: metav1.ObjectMeta{
Name: "someStack",
// Set invalid namespace here, because
// because cross-namespace controller
// references are not allowed
Namespace: "invalid-ns",
UID: "b23f9a38-9672-499f-8c29-15ede74d3ece",
},
}
// Create looks up the CR first, so we need to return our fake stack
k.GetStub = func(_ context.Context, name types.NamespacedName, object client.Object) error {
if r.Name == name.Name && r.Namespace == name.Namespace {
k.SetClientObject(object, &stack)
}
return nil
}
err := handlers.CreateOrUpdateLokiStack(context.TODO(), r, k, scheme)
// make sure error is returned to re-trigger reconciliation
require.Error(t, err)
}
func TestCreateOrUpdateLokiStack_WhenGetReturnsNoError_UpdateObjects(t *testing.T) {
k := &k8sfakes.FakeClient{}
r := ctrl.Request{
NamespacedName: types.NamespacedName{
Name: "my-stack",
Namespace: "some-ns",
},
}
stack := lokiv1beta1.LokiStack{
TypeMeta: metav1.TypeMeta{
Kind: "LokiStack",
},
ObjectMeta: metav1.ObjectMeta{
Name: "someStack",
Namespace: "some-ns",
UID: "b23f9a38-9672-499f-8c29-15ede74d3ece",
},
}
svc := corev1.Service{
TypeMeta: metav1.TypeMeta{
Kind: "Service",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "loki-gossip-ring-my-stack",
Namespace: "some-ns",
Labels: map[string]string{
"app.kubernetes.io/name": "loki",
"app.kubernetes.io/provider": "openshift",
"loki.grafana.com/name": "my-stack",
// Add custom label to fake semantic not equal
"test": "test",
},
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: "loki.openshift.io/v1beta1",
Kind: "LokiStack",
Name: "someStack",
UID: "b23f9a38-9672-499f-8c29-15ede74d3ece",
Controller: pointer.BoolPtr(true),
BlockOwnerDeletion: pointer.BoolPtr(true),
},
},
},
Spec: corev1.ServiceSpec{
ClusterIP: "None",
Ports: []corev1.ServicePort{
{
Name: "gossip",
Port: 7946,
Protocol: "TCP",
},
},
Selector: map[string]string{
"app.kubernetes.io/name": "loki",
"app.kubernetes.io/provider": "openshift",
"loki.grafana.com/name": "my-stack",
},
},
}
// Create looks up the CR first, so we need to return our fake stack
k.GetStub = func(_ context.Context, name types.NamespacedName, object client.Object) error {
if r.Name == name.Name && r.Namespace == name.Namespace {
k.SetClientObject(object, &stack)
}
if svc.Name == name.Name && svc.Namespace == name.Namespace {
k.SetClientObject(object, &svc)
}
return nil
}
err := handlers.CreateOrUpdateLokiStack(context.TODO(), r, k, scheme)
require.NoError(t, err)
// make sure create not called
require.Zero(t, k.CreateCallCount())
// make sure update was called
require.NotZero(t, k.UpdateCallCount())
}
func TestCreateOrUpdateLokiStack_WhenCreateReturnsError_ContinueWithOtherObjects(t *testing.T) {
k := &k8sfakes.FakeClient{}
r := ctrl.Request{
NamespacedName: types.NamespacedName{
Name: "my-stack",
Namespace: "some-ns",
},
}
stack := lokiv1beta1.LokiStack{
TypeMeta: metav1.TypeMeta{
Kind: "LokiStack",
},
ObjectMeta: metav1.ObjectMeta{
Name: "someStack",
Namespace: "some-ns",
UID: "b23f9a38-9672-499f-8c29-15ede74d3ece",
},
}
// GetStub looks up the CR first, so we need to return our fake stack
// return NotFound for everything else to trigger create.
k.GetStub = func(_ context.Context, name types.NamespacedName, object client.Object) error {
if r.Name == name.Name && r.Namespace == name.Namespace {
k.SetClientObject(object, &stack)
return nil
}
return apierrors.NewNotFound(schema.GroupResource{}, "something is not found")
}
// CreateStub returns an error for each resource to trigger reconciliation a new.
k.CreateStub = func(_ context.Context, o client.Object, _ ...client.CreateOption) error {
return apierrors.NewTooManyRequestsError("too many create requests")
}
err := handlers.CreateOrUpdateLokiStack(context.TODO(), r, k, scheme)
// make sure error is returned to re-trigger reconciliation
require.Error(t, err)
}
func TestCreateOrUpdateLokiStack_WhenUpdateReturnsError_ContinueWithOtherObjects(t *testing.T) {
k := &k8sfakes.FakeClient{}
r := ctrl.Request{
NamespacedName: types.NamespacedName{
Name: "my-stack",
Namespace: "some-ns",
},
}
stack := lokiv1beta1.LokiStack{
TypeMeta: metav1.TypeMeta{
Kind: "LokiStack",
},
ObjectMeta: metav1.ObjectMeta{
Name: "someStack",
Namespace: "some-ns",
UID: "b23f9a38-9672-499f-8c29-15ede74d3ece",
},
}
svc := corev1.Service{
TypeMeta: metav1.TypeMeta{
Kind: "Service",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "loki-gossip-ring-my-stack",
Namespace: "some-ns",
Labels: map[string]string{
"app.kubernetes.io/name": "loki",
"app.kubernetes.io/provider": "openshift",
"loki.grafana.com/name": "my-stack",
// Add custom label to fake semantic not equal
"test": "test",
},
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: "loki.openshift.io/v1beta1",
Kind: "LokiStack",
Name: "someStack",
UID: "b23f9a38-9672-499f-8c29-15ede74d3ece",
Controller: pointer.BoolPtr(true),
BlockOwnerDeletion: pointer.BoolPtr(true),
},
},
},
Spec: corev1.ServiceSpec{
ClusterIP: "None",
Ports: []corev1.ServicePort{
{
Name: "gossip",
Port: 7946,
Protocol: "TCP",
},
},
Selector: map[string]string{
"app.kubernetes.io/name": "loki",
"app.kubernetes.io/provider": "openshift",
"loki.grafana.com/name": "my-stack",
},
},
}
// GetStub looks up the CR first, so we need to return our fake stack
// return NotFound for everything else to trigger create.
k.GetStub = func(_ context.Context, name types.NamespacedName, object client.Object) error {
if r.Name == name.Name && r.Namespace == name.Namespace {
k.SetClientObject(object, &stack)
}
if svc.Name == name.Name && svc.Namespace == name.Namespace {
k.SetClientObject(object, &svc)
}
return nil
}
// CreateStub returns an error for each resource to trigger reconciliation a new.
k.UpdateStub = func(_ context.Context, o client.Object, _ ...client.UpdateOption) error {
return apierrors.NewTooManyRequestsError("too many create requests")
}
err := handlers.CreateOrUpdateLokiStack(context.TODO(), r, k, scheme)
// make sure error is returned to re-trigger reconciliation
require.Error(t, err)
}

@ -1,163 +0,0 @@
package handlers_test
import (
"context"
"errors"
"flag"
"io/ioutil"
"os"
"testing"
"github.com/ViaQ/logerr/log"
lokiv1beta1 "github.com/ViaQ/loki-operator/api/v1beta1"
"github.com/ViaQ/loki-operator/internal/external/k8s/k8sfakes"
"github.com/ViaQ/loki-operator/internal/handlers"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/utils/pointer"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
)
func TestMain(m *testing.M) {
testing.Init()
flag.Parse()
if testing.Verbose() {
// set to the highest for verbose testing
log.SetLogLevel(5)
} else {
if err := log.SetOutput(ioutil.Discard); err != nil {
// This would only happen if the default logger was changed which it hasn't so
// we can assume that a panic is necessary and the developer is to blame.
panic(err)
}
}
log.Init("testing")
os.Exit(m.Run())
}
func TestCreateLokiStack_WhenGetReturnsNotFound_DoesNotError(t *testing.T) {
k := &k8sfakes.FakeClient{}
r := ctrl.Request{
NamespacedName: types.NamespacedName{
Name: "my-stack",
Namespace: "some-ns",
},
}
k.GetStub = func(ctx context.Context, name types.NamespacedName, object client.Object) error {
return apierrors.NewNotFound(schema.GroupResource{}, "something wasn't found")
}
err := handlers.CreateLokiStack(context.TODO(), r, k)
require.NoError(t, err)
// make sure create was NOT called because the Get failed
require.Zero(t, k.CreateCallCount())
}
func TestCreateLokiStack_WhenGetReturnsAnErrorOtherThanNotFound_ReturnsTheError(t *testing.T) {
k := &k8sfakes.FakeClient{}
r := ctrl.Request{
NamespacedName: types.NamespacedName{
Name: "my-stack",
Namespace: "some-ns",
},
}
badRequestErr := apierrors.NewBadRequest("you do not belong here")
k.GetStub = func(ctx context.Context, name types.NamespacedName, object client.Object) error {
return badRequestErr
}
err := handlers.CreateLokiStack(context.TODO(), r, k)
require.Equal(t, badRequestErr, errors.Unwrap(err))
// make sure create was NOT called because the Get failed
require.Zero(t, k.CreateCallCount())
}
func TestCreateLokiStack_SetsNamespaceOnAllObjects(t *testing.T) {
k := &k8sfakes.FakeClient{}
r := ctrl.Request{
NamespacedName: types.NamespacedName{
Name: "my-stack",
Namespace: "some-ns",
},
}
k.CreateStub = func(_ context.Context, o client.Object, _ ...client.CreateOption) error {
assert.Equal(t, r.Namespace, o.GetNamespace())
return nil
}
err := handlers.CreateLokiStack(context.TODO(), r, k)
require.NoError(t, err)
// make sure create was called
require.NotZero(t, k.CreateCallCount())
}
func TestCreateLokiStack_SetsOwnerRefOnAllObjects(t *testing.T) {
k := &k8sfakes.FakeClient{}
r := ctrl.Request{
NamespacedName: types.NamespacedName{
Name: "my-stack",
Namespace: "some-ns",
},
}
stack := lokiv1beta1.LokiStack{
TypeMeta: metav1.TypeMeta{
Kind: "someKind",
},
ObjectMeta: metav1.ObjectMeta{
Name: "someStack",
Namespace: "someNamespace",
UID: "b23f9a38-9672-499f-8c29-15ede74d3ece",
},
}
// Create looks up the CR first, so we need to return our fake stack
k.GetStub = func(_ context.Context, _ types.NamespacedName, object client.Object) error {
k.SetClientObject(object, &stack)
return nil
}
expected := metav1.OwnerReference{
APIVersion: lokiv1beta1.GroupVersion.String(),
Kind: stack.Kind,
Name: stack.Name,
UID: stack.UID,
Controller: pointer.BoolPtr(true),
}
k.CreateStub = func(_ context.Context, o client.Object, _ ...client.CreateOption) error {
// OwnerRefs are appended so we have to find ours in the list
var ref metav1.OwnerReference
var found bool
for _, or := range o.GetOwnerReferences() {
if or.UID == stack.UID {
found = true
ref = or
break
}
}
require.True(t, found, "expected to find a matching ownerRef, but did not")
require.EqualValues(t, expected, ref)
return nil
}
err := handlers.CreateLokiStack(context.TODO(), r, k)
require.NoError(t, err)
// make sure create was called
require.NotZero(t, k.CreateCallCount())
}

@ -0,0 +1,84 @@
package manifests
import (
"reflect"
"github.com/ViaQ/logerr/kverrors"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)
// MutateFuncFor returns a mutate function based on the
// existing resource's concrete type. It supports currently
// only the following types or else panics:
// - ConfigMap
// - Service
// - Deployment
// - StatefulSet
func MutateFuncFor(existing, desired client.Object) controllerutil.MutateFn {
return func() error {
existing.SetAnnotations(desired.GetAnnotations())
existing.SetLabels(desired.GetLabels())
switch existing.(type) {
case *corev1.ConfigMap:
cm := existing.(*corev1.ConfigMap)
wantCm := desired.(*corev1.ConfigMap)
mutateConfigMap(cm, wantCm)
case *corev1.Service:
svc := existing.(*corev1.Service)
wantSvc := desired.(*corev1.Service)
mutateService(svc, wantSvc)
case *appsv1.Deployment:
dpl := existing.(*appsv1.Deployment)
wantDpl := desired.(*appsv1.Deployment)
mutateDeployment(dpl, wantDpl)
case *appsv1.StatefulSet:
sts := existing.(*appsv1.StatefulSet)
wantSts := desired.(*appsv1.StatefulSet)
mutateStatefulSet(sts, wantSts)
default:
t := reflect.TypeOf(existing).String()
return kverrors.New("missing mutate implementation for resource type", "type", t)
}
return nil
}
}
func mutateConfigMap(existing, desired *corev1.ConfigMap) {
existing.BinaryData = desired.BinaryData
}
func mutateService(existing, desired *corev1.Service) {
existing.Spec.Ports = desired.Spec.Ports
existing.Spec.Selector = desired.Spec.Selector
}
func mutateDeployment(existing, desired *appsv1.Deployment) {
// Deployment selector is immutable so we set this value only if
// a new object is going to be created
if existing.CreationTimestamp.IsZero() {
existing.Spec.Selector = desired.Spec.Selector
}
existing.Spec.Replicas = desired.Spec.Replicas
existing.Spec.Template = desired.Spec.Template
existing.Spec.Strategy = desired.Spec.Strategy
}
func mutateStatefulSet(existing, desired *appsv1.StatefulSet) {
// StatefulSet selector is immutable so we set this value only if
// a new object is going to be created
if existing.CreationTimestamp.IsZero() {
existing.Spec.Selector = desired.Spec.Selector
}
existing.Spec.PodManagementPolicy = desired.Spec.PodManagementPolicy
existing.Spec.Replicas = desired.Spec.Replicas
existing.Spec.Template = desired.Spec.Template
existing.Spec.VolumeClaimTemplates = desired.Spec.VolumeClaimTemplates
}

@ -0,0 +1,410 @@
package manifests
import (
"testing"
"github.com/stretchr/testify/require"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/utils/pointer"
)
func TestGetMutateFunc_MutateObjectMeta(t *testing.T) {
got := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"test": "test",
},
Annotations: map[string]string{
"test": "test",
},
},
}
want := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"test": "test",
},
Annotations: map[string]string{
"test": "test",
},
},
}
f := MutateFuncFor(got, want)
err := f()
require.NoError(t, err)
// Partial mutation checks
require.Exactly(t, got.Labels, want.Labels)
require.Exactly(t, got.Annotations, want.Annotations)
}
func TestGetMutateFunc_ReturnErrOnNotSupportedType(t *testing.T) {
got := &corev1.ServiceAccount{}
want := &corev1.ServiceAccount{}
f := MutateFuncFor(got, want)
require.Error(t, f())
}
func TestGetMutateFunc_MutateConfigMap(t *testing.T) {
got := &corev1.ConfigMap{
Data: map[string]string{"test": "remain"},
BinaryData: map[string][]byte{},
}
want := &corev1.ConfigMap{
Data: map[string]string{"test": "test"},
BinaryData: map[string][]byte{"btest": []byte("btestss")},
}
f := MutateFuncFor(got, want)
err := f()
require.NoError(t, err)
// Ensure partial mutation applied
require.Equal(t, got.Labels, want.Labels)
require.Equal(t, got.Annotations, want.Annotations)
require.Equal(t, got.BinaryData, got.BinaryData)
// Ensure not mutated
require.NotEqual(t, got.Data, want.Data)
}
func TestGetMutateFunc_MutateServiceSpec(t *testing.T) {
got := &corev1.Service{
Spec: corev1.ServiceSpec{
ClusterIP: "none",
ClusterIPs: []string{"8.8.8.8"},
Ports: []corev1.ServicePort{
{
Protocol: corev1.ProtocolTCP,
Port: 7777,
TargetPort: intstr.FromString("8888"),
},
},
Selector: map[string]string{
"select": "that",
},
},
}
want := &corev1.Service{
Spec: corev1.ServiceSpec{
ClusterIP: "none",
ClusterIPs: []string{"8.8.8.8", "9.9.9.9"},
Ports: []corev1.ServicePort{
{
Protocol: corev1.ProtocolTCP,
Port: 9999,
TargetPort: intstr.FromString("1111"),
},
},
Selector: map[string]string{
"select": "that",
"and": "other",
},
},
}
f := MutateFuncFor(got, want)
err := f()
require.NoError(t, err)
// Ensure partial mutation applied
require.ElementsMatch(t, got.Spec.Ports, want.Spec.Ports)
require.Exactly(t, got.Spec.Selector, want.Spec.Selector)
// Ensure not mutated
require.Equal(t, got.Spec.ClusterIP, "none")
require.Exactly(t, got.Spec.ClusterIPs, []string{"8.8.8.8"})
}
func TestGeMutateFunc_MutateDeploymentSpec(t *testing.T) {
type test struct {
name string
got *appsv1.Deployment
want *appsv1.Deployment
}
table := []test{
{
name: "initial creation",
got: &appsv1.Deployment{
Spec: appsv1.DeploymentSpec{
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"test": "test",
},
},
Replicas: pointer.Int32Ptr(1),
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{Name: "test"},
},
},
},
Strategy: appsv1.DeploymentStrategy{
Type: appsv1.RecreateDeploymentStrategyType,
},
},
},
want: &appsv1.Deployment{
Spec: appsv1.DeploymentSpec{
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"test": "test",
"and": "another",
},
},
Replicas: pointer.Int32Ptr(2),
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "test",
Args: []string{"--do-nothing"},
},
},
},
},
Strategy: appsv1.DeploymentStrategy{
Type: appsv1.RollingUpdateDeploymentStrategyType,
},
},
},
},
{
name: "update spec without selector",
got: &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{CreationTimestamp: metav1.Now()},
Spec: appsv1.DeploymentSpec{
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"test": "test",
},
},
Replicas: pointer.Int32Ptr(1),
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{Name: "test"},
},
},
},
Strategy: appsv1.DeploymentStrategy{
Type: appsv1.RecreateDeploymentStrategyType,
},
},
},
want: &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{CreationTimestamp: metav1.Now()},
Spec: appsv1.DeploymentSpec{
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"test": "test",
"and": "another",
},
},
Replicas: pointer.Int32Ptr(2),
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "test",
Args: []string{"--do-nothing"},
},
},
},
},
Strategy: appsv1.DeploymentStrategy{
Type: appsv1.RollingUpdateDeploymentStrategyType,
},
},
},
},
}
for _, tst := range table {
tst := tst
t.Run(tst.name, func(t *testing.T) {
t.Parallel()
f := MutateFuncFor(tst.got, tst.want)
err := f()
require.NoError(t, err)
// Ensure conditional mutation applied
if tst.got.CreationTimestamp.IsZero() {
require.Equal(t, tst.got.Spec.Selector, tst.want.Spec.Selector)
} else {
require.NotEqual(t, tst.got.Spec.Selector, tst.want.Spec.Selector)
}
// Ensure partial mutation applied
require.Equal(t, tst.got.Spec.Replicas, tst.want.Spec.Replicas)
require.Equal(t, tst.got.Spec.Template, tst.want.Spec.Template)
require.Equal(t, tst.got.Spec.Strategy, tst.want.Spec.Strategy)
})
}
}
func TestGeMutateFunc_MutateStatefulSetSpec(t *testing.T) {
type test struct {
name string
got *appsv1.StatefulSet
want *appsv1.StatefulSet
}
table := []test{
{
name: "initial creation",
got: &appsv1.StatefulSet{
Spec: appsv1.StatefulSetSpec{
PodManagementPolicy: appsv1.ParallelPodManagement,
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"test": "test",
},
},
Replicas: pointer.Int32Ptr(1),
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{Name: "test"},
},
},
},
VolumeClaimTemplates: []corev1.PersistentVolumeClaim{
{
Spec: corev1.PersistentVolumeClaimSpec{
AccessModes: []corev1.PersistentVolumeAccessMode{
corev1.ReadWriteOnce,
},
},
},
},
},
},
want: &appsv1.StatefulSet{
Spec: appsv1.StatefulSetSpec{
PodManagementPolicy: appsv1.OrderedReadyPodManagement,
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"test": "test",
"and": "another",
},
},
Replicas: pointer.Int32Ptr(2),
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "test",
Args: []string{"--do-nothing"},
},
},
},
},
VolumeClaimTemplates: []corev1.PersistentVolumeClaim{
{
Spec: corev1.PersistentVolumeClaimSpec{
AccessModes: []corev1.PersistentVolumeAccessMode{
corev1.ReadWriteOnce,
corev1.ReadOnlyMany,
},
},
},
},
},
},
},
{
name: "update spec without selector",
got: &appsv1.StatefulSet{
ObjectMeta: metav1.ObjectMeta{CreationTimestamp: metav1.Now()},
Spec: appsv1.StatefulSetSpec{
PodManagementPolicy: appsv1.ParallelPodManagement,
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"test": "test",
},
},
Replicas: pointer.Int32Ptr(1),
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{Name: "test"},
},
},
},
VolumeClaimTemplates: []corev1.PersistentVolumeClaim{
{
Spec: corev1.PersistentVolumeClaimSpec{
AccessModes: []corev1.PersistentVolumeAccessMode{
corev1.ReadWriteOnce,
},
},
},
},
},
},
want: &appsv1.StatefulSet{
ObjectMeta: metav1.ObjectMeta{CreationTimestamp: metav1.Now()},
Spec: appsv1.StatefulSetSpec{
PodManagementPolicy: appsv1.OrderedReadyPodManagement,
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"test": "test",
"and": "another",
},
},
Replicas: pointer.Int32Ptr(2),
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "test",
Args: []string{"--do-nothing"},
},
},
},
},
VolumeClaimTemplates: []corev1.PersistentVolumeClaim{
{
Spec: corev1.PersistentVolumeClaimSpec{
AccessModes: []corev1.PersistentVolumeAccessMode{
corev1.ReadWriteOnce,
corev1.ReadWriteMany,
},
},
},
},
},
},
},
}
for _, tst := range table {
tst := tst
t.Run(tst.name, func(t *testing.T) {
t.Parallel()
f := MutateFuncFor(tst.got, tst.want)
err := f()
require.NoError(t, err)
// Ensure conditional mutation applied
if tst.got.CreationTimestamp.IsZero() {
require.Equal(t, tst.got.Spec.Selector, tst.want.Spec.Selector)
} else {
require.NotEqual(t, tst.got.Spec.Selector, tst.want.Spec.Selector)
}
// Ensure partial mutation applied
require.Equal(t, tst.got.Spec.Replicas, tst.want.Spec.Replicas)
require.Equal(t, tst.got.Spec.Template, tst.want.Spec.Template)
require.Equal(t, tst.got.Spec.VolumeClaimTemplates, tst.got.Spec.VolumeClaimTemplates)
})
}
}

@ -197,7 +197,6 @@ func NewQuerierHTTPService(stackName string) *corev1.Service {
Labels: l,
},
Spec: corev1.ServiceSpec{
ClusterIP: "None",
Ports: []corev1.ServicePort{
{
Name: "http",

@ -179,7 +179,6 @@ func NewQueryFrontendHTTPService(stackName string) *corev1.Service {
Labels: l,
},
Spec: corev1.ServiceSpec{
ClusterIP: "None",
Ports: []corev1.ServicePort{
{
Name: "http",

Loading…
Cancel
Save