Skip to content
This repository has been archived by the owner on May 23, 2023. It is now read-only.

Commit

Permalink
Merge pull request #23 from vshn/feat/pg-reboot
Browse files Browse the repository at this point in the history
Add UpdateStrategy to VSHNPostgreSQL
  • Loading branch information
glrf authored May 8, 2023
2 parents f5c9857 + 0b3cf60 commit c382f5d
Show file tree
Hide file tree
Showing 9 changed files with 1,128 additions and 9 deletions.
143 changes: 143 additions & 0 deletions functions/vshn-postgres-func/restart.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package vshnpostgres

import (
"context"
"fmt"
"strconv"
"strings"
"time"

sgv1 "github.com/vshn/appcat-comp-functions/apis/stackgres/v1"
"github.com/vshn/appcat-comp-functions/runtime"
vshnv1 "github.com/vshn/component-appcat/apis/vshn/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/pointer"
controllerruntime "sigs.k8s.io/controller-runtime"
)

var sgDbOpsRestartRetention time.Duration = 30 * 24 * time.Hour

// TransformRestart triggers a restart of the postgreqsql instance if there is a pending restart and the composite is configured to restart on update.
func TransformRestart(ctx context.Context, iof *runtime.Runtime) runtime.Result {
return transformRestart(ctx, iof, time.Now)
}

func transformRestart(ctx context.Context, iof *runtime.Runtime, now func() time.Time) runtime.Result {
comp := vshnv1.VSHNPostgreSQL{}
err := iof.Desired.GetComposite(ctx, &comp)
if err != nil {
return runtime.NewFatal(ctx, err.Error())
}
err = keepRecentRestartOps(ctx, iof, comp.GetName(), now)
if err != nil {
return runtime.NewFatal(ctx, err.Error())
}

if comp.Spec.Parameters.UpdateStrategy.Type == vshnv1.VSHNPostgreSQLUpdateStrategyTypeOnRestart {
return runtime.NewNormal()
}

restartTime, err := getPendingRestart(ctx, iof)
if err != nil {
return runtime.NewWarning(ctx, err.Error())
}

if restartTime.IsZero() {
return runtime.NewNormal()
}

err = scheduleRestart(ctx, iof, comp.GetName(), restartTime)
if err != nil {
return runtime.NewFatal(ctx, err.Error())
}

return runtime.NewNormal()
}

func getPendingRestart(ctx context.Context, iof *runtime.Runtime) (time.Time, error) {
cluster := sgv1.SGCluster{}

err := iof.Observed.GetFromObject(ctx, &cluster, "cluster")
if err != nil {
return time.Time{}, err
}

log := controllerruntime.LoggerFrom(ctx).WithValues("cluster", cluster.Name)
if cluster.Status.Conditions == nil {
return time.Time{}, nil
}

for _, cond := range *cluster.Status.Conditions {
if cond.Type == nil || *cond.Type != sgv1.SGClusterConditionTypePendingRestart || cond.Status == nil || cond.LastTransitionTime == nil {
continue
}
status, err := strconv.ParseBool(*cond.Status)
if err != nil || !status {
continue
}

log.WithValues("at", cond.LastTransitionTime).Info("PendingRestart")

restartTime, err := time.Parse(time.RFC3339, *cond.LastTransitionTime)
if err != nil {
continue
}
return restartTime, nil
}

return time.Time{}, nil
}

func scheduleRestart(ctx context.Context, iof *runtime.Runtime, compName string, restartTime time.Time) error {
name := fmt.Sprintf("pg-restart-%d", restartTime.Unix())
ops := sgv1.SGDbOps{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: fmt.Sprintf("vshn-postgresql-%s", compName),
},
Spec: sgv1.SGDbOpsSpec{
Op: sgv1.SGDbOpsOpRestart,
SgCluster: compName,
Restart: &sgv1.SGDbOpsSpecRestart{
Method: pointer.String(sgv1.SGDbOpsRestartMethodInPlace),
OnlyPendingRestart: pointer.Bool(true),
},
RunAt: pointer.String(restartTime.Format(time.RFC3339)),
},
}
return iof.Desired.PutIntoObject(ctx, &ops, fmt.Sprintf("%s-%s", compName, name))
}

func keepRecentRestartOps(ctx context.Context, iof *runtime.Runtime, compName string, now func() time.Time) error {

for _, r := range iof.Observed.List(ctx) {
if !strings.HasPrefix(r.GetName(), fmt.Sprintf("%s-pg-restart", compName)) {
continue
}

op := sgv1.SGDbOps{}
err := iof.Observed.GetFromObject(ctx, &op, r.GetName())
if err != nil {
continue
}

if op.Spec.Op != "restart" || op.Spec.RunAt == nil {
continue
}

runAt, err := time.Parse(time.RFC3339, *op.Spec.RunAt)
if err != nil {
continue
}

if runAt.Before(now().Add(-1 * sgDbOpsRestartRetention)) {
continue
}

err = iof.Desired.PutIntoObject(ctx, &op, r.GetName())
if err != nil {
return err
}
}
return nil
}
92 changes: 92 additions & 0 deletions functions/vshn-postgres-func/restart_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package vshnpostgres

import (
"context"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
sgv1 "github.com/vshn/appcat-comp-functions/apis/stackgres/v1"
"github.com/vshn/appcat-comp-functions/runtime"
vshnv1 "github.com/vshn/component-appcat/apis/vshn/v1"

fnv1aplha1 "github.com/crossplane/crossplane/apis/apiextensions/fn/io/v1alpha1"
)

func TestTransformRestart_NoopNoPending(t *testing.T) {
iof := loadRuntimeFromFile(t, "restart/01-NoPendingReboot.yaml")
require.NoError(t, runtime.AddToScheme(sgv1.SchemeBuilder.SchemeBuilder))

res := TransformRestart(context.TODO(), iof)
assert.Equal(t, fnv1aplha1.SeverityNormal, res.Resolve().Severity, res.Resolve().Message)

comp := &vshnv1.XVSHNPostgreSQL{}
err := iof.Desired.GetComposite(context.TODO(), comp)
assert.NoError(t, err)

assert.Len(t, iof.Desired.List(context.TODO()), 0)
}
func TestTransformRestart_NoopPendingOnRestart(t *testing.T) {
iof := loadRuntimeFromFile(t, "restart/02-PendingRebootNoRestart.yaml")
require.NoError(t, runtime.AddToScheme(sgv1.SchemeBuilder.SchemeBuilder))

res := TransformRestart(context.TODO(), iof)
assert.Equal(t, fnv1aplha1.SeverityNormal, res.Resolve().Severity, res.Resolve().Message)

comp := &vshnv1.XVSHNPostgreSQL{}
err := iof.Desired.GetComposite(context.TODO(), comp)
assert.NoError(t, err)

assert.Len(t, iof.Desired.List(context.TODO()), 0)
}

func TestTransformRestart_RestartPending(t *testing.T) {
iof := loadRuntimeFromFile(t, "restart/02-PendingReboot.yaml")
require.NoError(t, runtime.AddToScheme(sgv1.SchemeBuilder.SchemeBuilder))

res := TransformRestart(context.TODO(), iof)
assert.Equal(t, fnv1aplha1.SeverityNormal, res.Resolve().Severity, res.Resolve().Message)

comp := &vshnv1.XVSHNPostgreSQL{}
err := iof.Desired.GetComposite(context.TODO(), comp)
assert.NoError(t, err)

assert.Len(t, iof.Desired.List(context.TODO()), 1)

sgrest := sgv1.SGDbOps{}
assert.NoError(t, iof.Desired.GetFromObject(context.TODO(), &sgrest, "pgsql-gc9x4-pg-restart-1682587342"))
assert.Equal(t, "restart", sgrest.Spec.Op)
if assert.NotNil(t, sgrest.Spec.RunAt) {
assert.Equal(t, "2023-04-27T09:22:22Z", *sgrest.Spec.RunAt)
}
if assert.NotNil(t, sgrest.Spec.Restart) {
if assert.NotNil(t, sgrest.Spec.Restart.Method) {
assert.Equal(t, "InPlace", *sgrest.Spec.Restart.Method)
}
if assert.NotNil(t, sgrest.Spec.Restart.OnlyPendingRestart) {
assert.True(t, *sgrest.Spec.Restart.OnlyPendingRestart)
}
}
assert.Equal(t, "restart", sgrest.Spec.Op)
}

func TestTransformRestart_KeepRecentReboots(t *testing.T) {
iof := loadRuntimeFromFile(t, "restart/03-KeepRecentReboots.yaml")
require.NoError(t, runtime.AddToScheme(sgv1.SchemeBuilder.SchemeBuilder))

now := func() time.Time {
n, err := time.Parse(time.RFC3339, "2023-04-27T10:04:05Z")
assert.NoError(t, err)
return n
}

res := transformRestart(context.TODO(), iof, now)
assert.Equal(t, fnv1aplha1.SeverityNormal, res.Resolve().Severity, res.Resolve().Message)

comp := &vshnv1.XVSHNPostgreSQL{}
err := iof.Desired.GetComposite(context.TODO(), comp)
assert.NoError(t, err)

assert.Len(t, iof.Desired.List(context.TODO()), 2)
}
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ require (
github.com/sethvargo/go-password v0.2.0
github.com/stretchr/testify v1.8.1
github.com/urfave/cli/v2 v2.23.7
github.com/vshn/component-appcat/apis v0.0.0-20230425231346-1af5398ee159
github.com/vshn/component-appcat/apis v0.0.0-20230508083110-a8e04b7b9a13
go.uber.org/zap v1.24.0
google.golang.org/grpc v1.50.1
k8s.io/api v0.26.3
Expand Down Expand Up @@ -74,7 +74,7 @@ require (
github.com/spf13/cobra v1.6.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/valyala/fasttemplate v1.2.1 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.8.0 // indirect
Expand Down
9 changes: 5 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -395,11 +395,12 @@ github.com/urfave/cli/v2 v2.23.7/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6f
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4=
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/vshn/component-appcat/apis v0.0.0-20230425231346-1af5398ee159 h1:mtiNMnPWcPWBS26ANetXB34cPp6WprCGPsLxWvshtDU=
github.com/vshn/component-appcat/apis v0.0.0-20230425231346-1af5398ee159/go.mod h1:CmhF7UOC1bivx++1x43BJfJaHj2eOG1d6GRJZQQoQvc=
github.com/vshn/component-appcat/apis v0.0.0-20230502150715-7f57fa574e3b h1:FYtjwjCM4iGpvLNxvZPV2c4PV+gSCtqkC3hjD3aRLxk=
github.com/vshn/component-appcat/apis v0.0.0-20230502150715-7f57fa574e3b/go.mod h1:+zmzGcEUhwhz3QlIMMo+XQTRSoIK8MY/kSlIcEzC6I8=
github.com/vshn/component-appcat/apis v0.0.0-20230508083110-a8e04b7b9a13 h1:GoRKKQzu7sUpp82v5+xqsIbWKtQZIv/vrS6+r5UzsaQ=
github.com/vshn/component-appcat/apis v0.0.0-20230508083110-a8e04b7b9a13/go.mod h1:+zmzGcEUhwhz3QlIMMo+XQTRSoIK8MY/kSlIcEzC6I8=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
Expand Down
17 changes: 14 additions & 3 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ import (
rt "runtime"
"time"

pb "github.com/crossplane/crossplane/apis/apiextensions/fn/proto/v1alpha1"
"github.com/go-logr/logr"
sgv1 "github.com/vshn/appcat-comp-functions/apis/stackgres/v1"
vp "github.com/vshn/appcat-comp-functions/functions/vshn-postgres-func"
"github.com/vshn/appcat-comp-functions/runtime"

pb "github.com/crossplane/crossplane/apis/apiextensions/fn/proto/v1alpha1"
"github.com/go-logr/logr"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
Expand All @@ -28,13 +30,17 @@ var AI = runtime.AppInfo{

var postgresFunctions = []runtime.Transform{
{
Name: "url-connection-details",
Name: "url-connection-detail",
TransformFunc: vp.AddUrlToConnectionDetails,
},
{
Name: "user-alerting",
TransformFunc: vp.AddUserAlerting,
},
{
Name: "restart",
TransformFunc: vp.TransformRestart,
},
{
Name: "random-default-schedule",
TransformFunc: vp.TransformSchedule,
Expand Down Expand Up @@ -107,6 +113,11 @@ func main() {
log.Fatalf("failed to listen: %v", err)
}

err = runtime.AddToScheme(sgv1.SchemeBuilder.SchemeBuilder)
if err != nil {
log.Fatalf("failed register stackgres CRDs: %v", err)
}

s := grpc.NewServer()

pb.RegisterContainerizedFunctionRunnerServiceServer(s, &server{logger: logger})
Expand Down
Loading

0 comments on commit c382f5d

Please sign in to comment.