Skip to content

Commit

Permalink
Added test cases
Browse files Browse the repository at this point in the history
  • Loading branch information
SrinivasAtmakuri committed Sep 4, 2023
1 parent 9f85cee commit 4960c23
Show file tree
Hide file tree
Showing 2 changed files with 644 additions and 63 deletions.
151 changes: 88 additions & 63 deletions pkg/frontend/admin_openshiftcluster_etcdcertificaterenew.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/sirupsen/logrus"
"github.com/ugorji/go/codec"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
kruntime "k8s.io/apimachinery/pkg/runtime"

Expand All @@ -37,6 +38,7 @@ type etcdrenew struct {
secretNames []string
mode string
backupSecrets map[string][]byte
lastRevision int32
}

var etcdOperatorControllerConditionsExpected = map[string]operatorv1.ConditionStatus{
Expand Down Expand Up @@ -79,7 +81,7 @@ func (e *etcdrenew) validate(ctx context.Context) error {

func (e *etcdrenew) isRenewed(ctx context.Context) error {
s := []steps.Step{
steps.Condition(e.isRevisied, 30*time.Minute, true),
steps.Condition(e.isRevisied, 1*time.Minute, true),
}
_, err := steps.Run(ctx, e.log, 10*time.Second, s, nil)
if err != nil {
Expand Down Expand Up @@ -120,18 +122,17 @@ func (f *frontend) _postAdminOpenShiftClusterEtcdCertificateRenew(ctx context.Co
return err
}
e := &etcdrenew{
log: log,
k: k,
secretNames: nil,
mode: "renew",
log: log,
k: k,
secretNames: nil,
mode: "renew",
backupSecrets: make(map[string][]byte),
lastRevision: 0,
}

if err = e.validateClusterVersion(ctx); err != nil {
return err
}
if err = e.validate(ctx); err != nil {
return err
}

// Fetch secretNames using nodeNames
masterNodeNames, err := fetchNodeNames(ctx, k, log)
Expand All @@ -148,15 +149,14 @@ func (f *frontend) _postAdminOpenShiftClusterEtcdCertificateRenew(ctx context.Co
}
}

if err = e.validate(ctx); err != nil {
return err
}
// backup and delete etcd secrets
if err = e.backupAndDelete(ctx); err != nil {
return err
}

// Calling Sleep method
e.log.Infoln("Entering sleep... 3mins")
time.Sleep(3 * time.Minute)

if err = e.isRenewed(ctx); err != nil {
e.mode = "recovery"
} else {
Expand Down Expand Up @@ -197,12 +197,40 @@ func (e *etcdrenew) validateClusterVersion(ctx context.Context) error {
return err
}
// ETCD ceritificates are autorotated by the operator when close to expiry for cluster running 4.9+
if clusterVersion.Lt(version.NewVersion(4, 9)) {
if !clusterVersion.Lt(version.NewVersion(4, 9)) {
return api.NewCloudError(http.StatusForbidden, api.CloudErrorCodeForbidden, "", "etcd certificate renewal is not needed for cluster running version 4.9+")
}
return nil
}

func fetchNodeNames(ctx context.Context, k adminactions.KubeActions, log *logrus.Entry) ([]string, error) {
var masterNodeNames []string
var u unstructured.Unstructured
var nodes corev1.NodeList

log.Infoln("fetching node names")

nodeList, err := k.KubeList(ctx, "node", "")
if err != nil {
return nil, err
}
if err = json.Unmarshal(nodeList, &u); err != nil {
return nil, err
}
err = kruntime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &nodes)
if err != nil {
return nil, err
}

for _, node := range nodes.Items {
if _, ok := node.ObjectMeta.Labels["node-role.kubernetes.io/master"]; ok {
masterNodeNames = append(masterNodeNames, node.ObjectMeta.Name)
continue
}
}
return masterNodeNames, nil
}

func (e *etcdrenew) validateEtcdOperatorControllersState(ctx context.Context) error {
e.log.Infoln("validating etcdOperator Controllers state now")
rawEtcd, err := e.k.KubeGet(ctx, "Etcd", "", "cluster")
Expand All @@ -218,30 +246,15 @@ func (e *etcdrenew) validateEtcdOperatorControllersState(ctx context.Context) er
if _, ok := etcdOperatorControllerConditionsExpected[c.Type]; !ok {
continue
}
if etcdOperatorControllerConditionsExpected[c.Type] != c.Status && e.mode == "renewed" {
if etcdOperatorControllerConditionsExpected[c.Type] != c.Status {
return api.NewCloudError(http.StatusInternalServerError, api.CloudErrorCodeInternalServerError, "", "%s is in state %s, quiting.", c.Type, c.Status)
}
}
return nil
}

func (e *etcdrenew) isRevisied(ctx context.Context) (bool, error) {
isAtRevision := true
rawEtcd, err := e.k.KubeGet(ctx, "Etcd", "", "cluster")
if err != nil {
return false, api.NewCloudError(http.StatusInternalServerError, api.CloudErrorCodeInternalServerError, "", err.Error())
if e.mode == "renew" {
e.lastRevision = etcd.Status.LatestAvailableRevision
}
etcd := &operatorv1.Etcd{}
err = codec.NewDecoderBytes(rawEtcd, &codec.JsonHandle{}).Decode(etcd)
if err != nil {
return false, api.NewCloudError(http.StatusInternalServerError, api.CloudErrorCodeInternalServerError, "", fmt.Sprintf("failed to decode etcd object, %s", err.Error()))
}
for _, s := range etcd.Status.NodeStatuses {
if s.CurrentRevision != etcd.Status.LatestAvailableRevision {
isAtRevision = false
}
}
return isAtRevision, nil

return nil
}

func (e *etcdrenew) validateEtcdOperatorState(ctx context.Context) error {
Expand All @@ -259,39 +272,13 @@ func (e *etcdrenew) validateEtcdOperatorState(ctx context.Context) error {
if _, ok := etcdOperatorConditionsExpected[c.Type]; !ok {
continue
}
if etcdOperatorConditionsExpected[c.Type] != c.Status && e.mode == "renewed" {
if etcdOperatorConditionsExpected[c.Type] != c.Status {
return api.NewCloudError(http.StatusInternalServerError, api.CloudErrorCodeInternalServerError, "", "Etcd Operator is not in expected state, quiting.")
}
}
return nil
}

func fetchNodeNames(ctx context.Context, k adminactions.KubeActions, log *logrus.Entry) ([]string, error) {
var masterNodeNames []string
var u unstructured.Unstructured
var nodes corev1.NodeList

nodeList, err := k.KubeList(ctx, "node", "")
if err != nil {
return nil, err
}
if err = json.Unmarshal(nodeList, &u); err != nil {
return nil, err
}
err = kruntime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &nodes)
if err != nil {
return nil, err
}

for _, node := range nodes.Items {
if _, ok := node.ObjectMeta.Labels["node-role.kubernetes.io/master"]; ok {
masterNodeNames = append(masterNodeNames, node.ObjectMeta.Name)
continue
}
}
return masterNodeNames, nil
}

func (e *etcdrenew) validateEtcdCertsExistsAndExpiry(ctx context.Context) error {
e.log.Infoln("validating etcd certs exists, not expired but are not close to expiry")
for _, secretname := range e.secretNames {
Expand All @@ -313,11 +300,11 @@ func (e *etcdrenew) validateEtcdCertsExistsAndExpiry(ctx context.Context) error
if err != nil {
return err
}
if !utilcert.IsLessThanMinimumDuration(certData[0], utilcert.DefaultMinDurationPercent) && e.mode == "renewed" {
return api.NewCloudError(http.StatusInternalServerError, api.CloudErrorCodeInternalServerError, "", "secret %s is not near expiry, quitting", secretname)
if !utilcert.IsLessThanMinimumDuration(certData[0], utilcert.DefaultMinDurationPercent) && e.mode != "renewed" {
return api.NewCloudError(http.StatusInternalServerError, api.CloudErrorCodeInternalServerError, "", "secret %s is not near expiry, quitting.", secretname)
}
if utilcert.IsCertExpired(certData[0]) {
return api.NewCloudError(http.StatusInternalServerError, api.CloudErrorCodeInternalServerError, "", "secret %s is already expired, quitting", secretname)
return api.NewCloudError(http.StatusInternalServerError, api.CloudErrorCodeInternalServerError, "", "secret %s is already expired, quitting.", secretname)
}
}

Expand All @@ -332,6 +319,22 @@ func (e *etcdrenew) backupEtcdSecrets(ctx context.Context) error {
if err != nil {
return err
}
secret := &corev1.Secret{}
err = codec.NewDecoderBytes(data, &codec.JsonHandle{}).Decode(secret)
if err != nil {
return api.NewCloudError(http.StatusInternalServerError, api.CloudErrorCodeInternalServerError, "", fmt.Sprintf("failed to decode secret, %s", err.Error()))
}
secret.CreationTimestamp = metav1.Time{
Time: time.Now(),
}
secret.ResourceVersion = ""
secret.SelfLink = ""

Check failure on line 331 in pkg/frontend/admin_openshiftcluster_etcdcertificaterenew.go

View workflow job for this annotation

GitHub Actions / golangci-lint

SA1019: secret.SelfLink is deprecated: selfLink is a legacy read-only field that is no longer populated by the system. +optional (staticcheck)
secret.UID = ""

err = codec.NewEncoderBytes(&data, &codec.JsonHandle{}).Encode(secret)
if err != nil {
return api.NewCloudError(http.StatusInternalServerError, api.CloudErrorCodeInternalServerError, "", fmt.Sprintf("failed to encode secret, %s", err.Error()))
}
e.backupSecrets[secretname] = data
}
return nil
Expand All @@ -349,6 +352,28 @@ func (e *etcdrenew) deleteEtcdSecrets(ctx context.Context) error {
return nil
}

func (e *etcdrenew) isRevisied(ctx context.Context) (bool, error) {
isAtRevision := true
rawEtcd, err := e.k.KubeGet(ctx, "Etcd", "", "cluster")
if err != nil {
return false, api.NewCloudError(http.StatusInternalServerError, api.CloudErrorCodeInternalServerError, "", err.Error())
}
etcd := &operatorv1.Etcd{}
err = codec.NewDecoderBytes(rawEtcd, &codec.JsonHandle{}).Decode(etcd)
if err != nil {
return false, api.NewCloudError(http.StatusInternalServerError, api.CloudErrorCodeInternalServerError, "", fmt.Sprintf("failed to decode etcd object, %s", err.Error()))
}
if e.lastRevision != etcd.Status.LatestAvailableRevision {
return false, nil
}
for _, s := range etcd.Status.NodeStatuses {
if s.CurrentRevision != etcd.Status.LatestAvailableRevision {
isAtRevision = false
}
}
return isAtRevision, nil
}

func (e *etcdrenew) recoverEtcdSecrets(ctx context.Context) error {
e.log.Infoln("recovering etcd secrets now")
for secretname, data := range e.backupSecrets {
Expand Down
Loading

0 comments on commit 4960c23

Please sign in to comment.