Skip to content

Commit

Permalink
Refactor Role Helm charts into component (#538)
Browse files Browse the repository at this point in the history
* Add role component
- Remove Role from template yaml from chart
- Add utils function `GetRoleName`
- Removed old `reconcileRole`
- Add unit tests for Role component

* Add unit tests for Etcd helper functions

- `GetPeerServiceName`
- `GetClientServiceName`
- `GetServiceAccountName`
- `GetConfigmapName`
- `GetCompactionJobName`
- `GetOrdinalPodName`
- `GetDeltaSnapshotLeaseName`
- `GetFullSnapshotLeaseName`
- `GetDefaultLabels`
- `GetAsOwnerReference`
- `GetRoleName`

* Updated unit tests for checking all elements in Rules slice with MatchAllElements
  • Loading branch information
seshachalam-yv authored Apr 11, 2023
1 parent 200df4b commit ebc8311
Show file tree
Hide file tree
Showing 11 changed files with 560 additions and 95 deletions.
5 changes: 5 additions & 0 deletions api/v1alpha1/types_etcd.go
Original file line number Diff line number Diff line change
Expand Up @@ -470,3 +470,8 @@ func (e *Etcd) GetAsOwnerReference() metav1.OwnerReference {
BlockOwnerDeletion: pointer.Bool(true),
}
}

// GetRoleName returns the role name for the Etcd
func (e *Etcd) GetRoleName() string {
return fmt.Sprintf("druid.gardener.cloud:etcd:%s", e.Name)
}
92 changes: 82 additions & 10 deletions api/v1alpha1/types_etcd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/utils/pointer"

. "github.com/gardener/etcd-druid/api/v1alpha1"
)
Expand All @@ -42,17 +43,9 @@ var _ = Describe("Etcd", func() {
)

BeforeEach(func() {
// Add any setup steps that needs to be executed before each test
created = getEtcd("foo", "default")
})

AfterEach(func() {
// Add any teardown steps that needs to be executed after each test
})

// Add Tests for OpenAPI validation (or additional CRD features) specified in
// your API definition.
// Avoid adding tests for vanilla CRUD operations because they would
// test Kubernetes API server, which isn't the goal here.
Context("Create API", func() {

It("should create an object successfully", func() {
Expand All @@ -61,7 +54,6 @@ var _ = Describe("Etcd", func() {
Name: "foo",
Namespace: "default",
}
created = getEtcd("foo", "default")

By("creating an API obj")
Expect(k8sClient.Create(context.Background(), created)).To(Succeed())
Expand All @@ -77,6 +69,85 @@ var _ = Describe("Etcd", func() {

})

Context("GetPeerServiceName", func() {
It("should return the correct peer service name", func() {
Expect(created.GetPeerServiceName()).To(Equal("foo-peer"))
})
})

Context("GetClientServiceName", func() {
It("should return the correct client service name", func() {
Expect(created.GetClientServiceName()).To(Equal("foo-client"))
})
})

Context("GetServiceAccountName", func() {
It("should return the correct service account name", func() {
Expect(created.GetServiceAccountName()).To(Equal("foo"))
})
})

Context("GetConfigmapName", func() {
It("should return the correct configmap name", func() {
Expect(created.GetConfigmapName()).To(Equal("etcd-bootstrap-123456"))
})
})

Context("GetCompactionJobName", func() {
It("should return the correct compaction job name", func() {
Expect(created.GetCompactionJobName()).To(Equal("123456-compact-job"))
})
})

Context("GetOrdinalPodName", func() {
It("should return the correct ordinal pod name", func() {
Expect(created.GetOrdinalPodName(0)).To(Equal("foo-0"))
})
})

Context("GetDeltaSnapshotLeaseName", func() {
It("should return the correct delta snapshot lease name", func() {
Expect(created.GetDeltaSnapshotLeaseName()).To(Equal("foo-delta-snap"))
})
})

Context("GetFullSnapshotLeaseName", func() {
It("should return the correct full snapshot lease name", func() {
Expect(created.GetFullSnapshotLeaseName()).To(Equal("foo-full-snap"))
})
})

Context("GetDefaultLabels", func() {
It("should return the default labels for etcd", func() {
expected := map[string]string{
"name": "etcd",
"instance": "foo",
}
Expect(created.GetDefaultLabels()).To(Equal(expected))
})
})

Context("GetAsOwnerReference", func() {
It("should return an OwnerReference object that represents the current Etcd instance", func() {

expected := metav1.OwnerReference{
APIVersion: GroupVersion.String(),
Kind: "Etcd",
Name: "foo",
UID: "123456",
Controller: pointer.Bool(true),
BlockOwnerDeletion: pointer.Bool(true),
}
Expect(created.GetAsOwnerReference()).To(Equal(expected))
})
})

Context("GetRoleName", func() {
It("should return the role name for the Etcd", func() {
expected := "druid.gardener.cloud:etcd:foo"
Expect(created.GetRoleName()).To(Equal(expected))
})
})
})

func getEtcd(name, namespace string) *Etcd {
Expand Down Expand Up @@ -135,6 +206,7 @@ func getEtcd(name, namespace string) *Etcd {
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
UID: "123456",
},
Spec: EtcdSpec{
Annotations: map[string]string{
Expand Down
47 changes: 0 additions & 47 deletions charts/etcd/templates/etcd-role.yaml

This file was deleted.

51 changes: 14 additions & 37 deletions controllers/etcd/reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
componentconfigmap "github.com/gardener/etcd-druid/pkg/component/etcd/configmap"
componentlease "github.com/gardener/etcd-druid/pkg/component/etcd/lease"
componentpdb "github.com/gardener/etcd-druid/pkg/component/etcd/poddisruptionbudget"
componentrole "github.com/gardener/etcd-druid/pkg/component/etcd/role"
componentservice "github.com/gardener/etcd-druid/pkg/component/etcd/service"
componentserviceaccount "github.com/gardener/etcd-druid/pkg/component/etcd/serviceaccount"
componentsts "github.com/gardener/etcd-druid/pkg/component/etcd/statefulset"
Expand Down Expand Up @@ -271,6 +272,14 @@ func (r *Reconciler) delete(ctx context.Context, etcd *druidv1alpha1.Etcd) (ctrl
}, err
}

roleValues := componentrole.GenerateValues(etcd)
roleDeployer := componentrole.New(r.Client, roleValues)
if err := roleDeployer.Destroy(ctx); err != nil {
return ctrl.Result{
Requeue: true,
}, err
}

if sets.NewString(etcd.Finalizers...).Has(common.FinalizerName) {
logger.Info("Removing finalizer")
if err := controllerutils.RemoveFinalizers(ctx, r.Client, etcd, common.FinalizerName); client.IgnoreNotFound(err) != nil {
Expand All @@ -283,39 +292,6 @@ func (r *Reconciler) delete(ctx context.Context, etcd *druidv1alpha1.Etcd) (ctrl
return ctrl.Result{}, nil
}

func (r *Reconciler) reconcileRole(ctx context.Context, logger logr.Logger, etcd *druidv1alpha1.Etcd, values map[string]interface{}) error {
logger.Info("Reconciling role")
var err error

decoded, err := r.chart.decodeRole(etcd.Name, etcd.Namespace, values)
if err != nil {
return err
}

roleObj := &rbac.Role{}
key := client.ObjectKeyFromObject(decoded)
if err := r.Get(ctx, key, roleObj); err != nil {
if !apierrors.IsNotFound(err) {
return err
}
if err := r.Create(ctx, decoded); err != nil {
return err
}
logger.Info("Creating role", "role", kutil.Key(decoded.Namespace, decoded.Name).String())
return nil
}

if !reflect.DeepEqual(decoded.Rules, roleObj.Rules) {
roleObjCopy := roleObj.DeepCopy()
roleObjCopy.Rules = decoded.Rules
if err := r.Patch(ctx, roleObjCopy, client.MergeFrom(roleObj)); err != nil {
return err
}
}

return nil
}

func (r *Reconciler) reconcileRoleBinding(ctx context.Context, logger logr.Logger, etcd *druidv1alpha1.Etcd, values map[string]interface{}) error {
logger.Info("Reconciling rolebinding")
var err error
Expand Down Expand Up @@ -398,17 +374,18 @@ func (r *Reconciler) reconcileEtcd(ctx context.Context, logger logr.Logger, etcd
return reconcileResult{err: err}
}

roleValues, err := r.getMapFromEtcd(etcd, r.config.DisableEtcdServiceAccountAutomount)
roleValues := componentrole.GenerateValues(etcd)
roleDeployer := componentrole.New(r.Client, roleValues)
err = roleDeployer.Deploy(ctx)
if err != nil {
return reconcileResult{err: err}
}

err = r.reconcileRole(ctx, logger, etcd, roleValues)
roleBindingValues, err := r.getMapFromEtcd(etcd, r.config.DisableEtcdServiceAccountAutomount)
if err != nil {
return reconcileResult{err: err}
}

err = r.reconcileRoleBinding(ctx, logger, etcd, roleValues)
err = r.reconcileRoleBinding(ctx, logger, etcd, roleBindingValues)
if err != nil {
return reconcileResult{err: err}
}
Expand Down
65 changes: 65 additions & 0 deletions pkg/component/etcd/role/role.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright (c) 2023 SAP SE or an SAP affiliate company. All rights reserved. This file is licensed under the Apache Software License, v. 2 except as noted otherwise in the LICENSE file
//
// 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 role

import (
"context"

"github.com/gardener/gardener/pkg/controllerutils"

gardenercomponent "github.com/gardener/gardener/pkg/operation/botanist/component"
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
)

type component struct {
client client.Client
values *Values
}

func (c component) Deploy(ctx context.Context) error {
role := c.emptyRole()
_, err := controllerutils.GetAndCreateOrStrategicMergePatch(ctx, c.client, role, func() error {
role.Name = c.values.Name
role.Namespace = c.values.Namespace
role.Labels = c.values.Labels
role.OwnerReferences = c.values.OwnerReferences
role.Rules = c.values.Rules
return nil
})
return err
}

func (c component) Destroy(ctx context.Context) error {
return client.IgnoreNotFound(c.client.Delete(ctx, c.emptyRole()))
}

func (c component) emptyRole() *rbacv1.Role {
return &rbacv1.Role{
ObjectMeta: metav1.ObjectMeta{
Name: c.values.Name,
Namespace: c.values.Namespace,
},
}
}

// New creates a new role deployer instance.
func New(c client.Client, values *Values) gardenercomponent.Deployer {
return &component{
client: c,
values: values,
}
}
27 changes: 27 additions & 0 deletions pkg/component/etcd/role/role_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) 2023 SAP SE or an SAP affiliate company. All rights reserved. This file is licensed under the Apache Software License, v. 2 except as noted otherwise in the LICENSE file
//
// 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 role

import (
"testing"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

func TestService(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Role Component Suite")
}
Loading

0 comments on commit ebc8311

Please sign in to comment.