1
0
Fork 0
mirror of https://github.com/arangodb/kube-arangodb.git synced 2024-12-14 11:57:37 +00:00

[Feature] Create local variables for an action (#978)

This commit is contained in:
Tomasz Mielech 2022-05-08 16:02:30 +02:00 committed by GitHub
parent 66ff115ee1
commit aad986fed8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 633 additions and 12 deletions

View file

@ -6,6 +6,7 @@
- (Refactor) Anonymous inspector functions
- (Feature) Recursive OwnerReference discovery
- (Maintenance) Add check make targets
- (Feature) Create support for local variables in actions.
## [1.2.11](https://github.com/arangodb/kube-arangodb/tree/1.2.11) (2022-04-30)
- (Bugfix) Orphan PVC are not removed

View file

@ -82,7 +82,7 @@ ifeq ($(DEBUG),true)
DEBUG := true
DOCKERFILE := Dockerfile.debug
# required by DLV https://github.com/go-delve/delve/blob/master/Documentation/usage/dlv_exec.md
COMPILE_DEBUG_FLAGS := -gcflags="all=-N -l"
COMPILE_DEBUG_FLAGS := -gcflags="all=-N -l" -ldflags "-extldflags '-static'"
else
DEBUG := false
DOCKERFILE := Dockerfile
@ -522,4 +522,4 @@ check-community:
@$(MAKE) _check RELEASE_MODE=community
_check:
@$(MAKE) fmt license-verify linter run-unit-tests bin
@$(MAKE) fmt license-verify linter run-unit-tests bin

View file

@ -21,12 +21,13 @@
package v1
import (
"github.com/arangodb/kube-arangodb/pkg/util"
"github.com/dchest/uniuri"
"k8s.io/apimachinery/pkg/api/equality"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/uuid"
"github.com/arangodb/kube-arangodb/pkg/util"
)
// ActionPriority define action priority
@ -225,6 +226,8 @@ type Action struct {
Image string `json:"image,omitempty"`
// Params additional parameters used for action
Params map[string]string `json:"params,omitempty"`
// Locals additional storage for local variables which are produced during the action.
Locals PlanLocals `json:"locals,omitempty"`
}
// Equal compares two Actions
@ -237,7 +240,8 @@ func (a Action) Equal(other Action) bool {
util.TimeCompareEqualPointer(a.StartTime, other.StartTime) &&
a.Reason == other.Reason &&
a.Image == other.Image &&
equality.Semantic.DeepEqual(a.Params, other.Params)
equality.Semantic.DeepEqual(a.Params, other.Params) &&
a.Locals.Equal(other.Locals)
}
// AddParam returns copy of action with set parameter

View file

@ -0,0 +1,114 @@
//
// DISCLAIMER
//
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany
//
// 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.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
package v1
type PlanLocalKey string
func (p PlanLocalKey) String() string {
return string(p)
}
type PlanLocals map[PlanLocalKey]string
func (p *PlanLocals) Remove(key PlanLocalKey) bool {
if *p == nil {
return false
}
z := *p
if _, ok := z[key]; ok {
delete(z, key)
*p = z
return true
}
return false
}
func (p PlanLocals) Get(key PlanLocalKey) (string, bool) {
v, ok := p[key]
return v, ok
}
func (p PlanLocals) GetWithParent(parent PlanLocals, key PlanLocalKey) (string, bool) {
v, ok := p[key]
if ok {
return v, true
}
return parent.Get(key)
}
func (p *PlanLocals) Merge(merger PlanLocals) (changed bool) {
for k, v := range merger {
if p.Add(k, v, true) {
changed = true
}
}
return
}
func (p *PlanLocals) Add(key PlanLocalKey, value string, override bool) bool {
if value == "" {
return p.Remove(key)
}
if *p == nil {
*p = PlanLocals{
key: value,
}
return true
}
z := *p
if v, ok := z[key]; ok {
if v == value {
return true
}
if !override {
return false
}
}
z[key] = value
*p = z
return true
}
func (p PlanLocals) Equal(other PlanLocals) bool {
if len(p) != len(other) {
return false
}
for k, v := range p {
if v2, ok := other[k]; !ok || v != v2 {
return false
}
}
return true
}

View file

@ -0,0 +1,148 @@
//
// DISCLAIMER
//
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany
//
// 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.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
package v1
import (
"testing"
"github.com/stretchr/testify/require"
)
func Test_PlanLocals(t *testing.T) {
var l PlanLocals
var key PlanLocalKey = "test"
v1, v2 := "v1", "v2"
t.Run("Get on nil", func(t *testing.T) {
v, ok := l.Get(key)
require.Equal(t, "", v)
require.False(t, ok)
})
t.Run("Remove on nil", func(t *testing.T) {
ok := l.Remove(key)
require.False(t, ok)
})
t.Run("Add", func(t *testing.T) {
ok := l.Add(key, v1, false)
require.True(t, ok)
v, ok := l.Get(key)
require.True(t, ok)
require.Equal(t, v1, v)
})
t.Run("Update", func(t *testing.T) {
ok := l.Add(key, v2, false)
require.False(t, ok)
v, ok := l.Get(key)
require.True(t, ok)
require.Equal(t, v1, v)
})
t.Run("Update - override", func(t *testing.T) {
ok := l.Add(key, v2, true)
require.True(t, ok)
v, ok := l.Get(key)
require.True(t, ok)
require.Equal(t, v2, v)
})
t.Run("Remove", func(t *testing.T) {
ok := l.Remove(key)
require.True(t, ok)
})
t.Run("Remove missing", func(t *testing.T) {
ok := l.Remove(key)
require.False(t, ok)
})
}
func Test_PlanLocals_Equal(t *testing.T) {
cmp := func(name string, a, b PlanLocals, expected bool) {
t.Run(name, func(t *testing.T) {
require.True(t, a.Equal(a))
require.True(t, b.Equal(b))
if expected {
require.True(t, a.Equal(b))
require.True(t, b.Equal(a))
} else {
require.False(t, a.Equal(b))
require.False(t, b.Equal(a))
}
})
}
cmp("Nil", nil, nil, true)
cmp("Nil & empty", nil, PlanLocals{}, true)
cmp("Empty", PlanLocals{}, PlanLocals{}, true)
cmp("Same keys & values", PlanLocals{
"key1": "v1",
}, PlanLocals{
"key1": "v1",
}, true)
cmp("Diff keys", PlanLocals{
"key2": "v1",
}, PlanLocals{
"key1": "v1",
}, false)
cmp("Same keys & diff values", PlanLocals{
"key1": "v1",
}, PlanLocals{
"key1": "v2",
}, false)
cmp("Same multi keys & values", PlanLocals{
"key1": "v1",
"ket2": "v2",
}, PlanLocals{
"key1": "v1",
"ket2": "v2",
}, true)
cmp("Same multi keys & values - reorder", PlanLocals{
"key1": "v1",
"ket2": "v2",
}, PlanLocals{
"ket2": "v2",
"key1": "v1",
}, true)
}

View file

@ -49,6 +49,13 @@ func (in *Action) DeepCopyInto(out *Action) {
(*out)[key] = val
}
}
if in.Locals != nil {
in, out := &in.Locals, &out.Locals
*out = make(PlanLocals, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
return
}
@ -1852,6 +1859,28 @@ func (in Plan) DeepCopy() Plan {
return *out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in PlanLocals) DeepCopyInto(out *PlanLocals) {
{
in := &in
*out = make(PlanLocals, len(*in))
for key, val := range *in {
(*out)[key] = val
}
return
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlanLocals.
func (in PlanLocals) DeepCopy() PlanLocals {
if in == nil {
return nil
}
out := new(PlanLocals)
in.DeepCopyInto(out)
return *out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *RocksDBEncryptionSpec) DeepCopyInto(out *RocksDBEncryptionSpec) {
*out = *in

View file

@ -21,12 +21,13 @@
package v2alpha1
import (
"github.com/arangodb/kube-arangodb/pkg/util"
"github.com/dchest/uniuri"
"k8s.io/apimachinery/pkg/api/equality"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/uuid"
"github.com/arangodb/kube-arangodb/pkg/util"
)
// ActionPriority define action priority
@ -225,6 +226,8 @@ type Action struct {
Image string `json:"image,omitempty"`
// Params additional parameters used for action
Params map[string]string `json:"params,omitempty"`
// Locals additional storage for local variables which are produced during the action.
Locals PlanLocals `json:"locals,omitempty"`
}
// Equal compares two Actions
@ -237,7 +240,8 @@ func (a Action) Equal(other Action) bool {
util.TimeCompareEqualPointer(a.StartTime, other.StartTime) &&
a.Reason == other.Reason &&
a.Image == other.Image &&
equality.Semantic.DeepEqual(a.Params, other.Params)
equality.Semantic.DeepEqual(a.Params, other.Params) &&
a.Locals.Equal(other.Locals)
}
// AddParam returns copy of action with set parameter

View file

@ -0,0 +1,114 @@
//
// DISCLAIMER
//
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany
//
// 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.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
package v2alpha1
type PlanLocalKey string
func (p PlanLocalKey) String() string {
return string(p)
}
type PlanLocals map[PlanLocalKey]string
func (p *PlanLocals) Remove(key PlanLocalKey) bool {
if *p == nil {
return false
}
z := *p
if _, ok := z[key]; ok {
delete(z, key)
*p = z
return true
}
return false
}
func (p PlanLocals) Get(key PlanLocalKey) (string, bool) {
v, ok := p[key]
return v, ok
}
func (p PlanLocals) GetWithParent(parent PlanLocals, key PlanLocalKey) (string, bool) {
v, ok := p[key]
if ok {
return v, true
}
return parent.Get(key)
}
func (p *PlanLocals) Merge(merger PlanLocals) (changed bool) {
for k, v := range merger {
if p.Add(k, v, true) {
changed = true
}
}
return
}
func (p *PlanLocals) Add(key PlanLocalKey, value string, override bool) bool {
if value == "" {
return p.Remove(key)
}
if *p == nil {
*p = PlanLocals{
key: value,
}
return true
}
z := *p
if v, ok := z[key]; ok {
if v == value {
return true
}
if !override {
return false
}
}
z[key] = value
*p = z
return true
}
func (p PlanLocals) Equal(other PlanLocals) bool {
if len(p) != len(other) {
return false
}
for k, v := range p {
if v2, ok := other[k]; !ok || v != v2 {
return false
}
}
return true
}

View file

@ -0,0 +1,148 @@
//
// DISCLAIMER
//
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany
//
// 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.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
package v2alpha1
import (
"testing"
"github.com/stretchr/testify/require"
)
func Test_PlanLocals(t *testing.T) {
var l PlanLocals
var key PlanLocalKey = "test"
v1, v2 := "v1", "v2"
t.Run("Get on nil", func(t *testing.T) {
v, ok := l.Get(key)
require.Equal(t, "", v)
require.False(t, ok)
})
t.Run("Remove on nil", func(t *testing.T) {
ok := l.Remove(key)
require.False(t, ok)
})
t.Run("Add", func(t *testing.T) {
ok := l.Add(key, v1, false)
require.True(t, ok)
v, ok := l.Get(key)
require.True(t, ok)
require.Equal(t, v1, v)
})
t.Run("Update", func(t *testing.T) {
ok := l.Add(key, v2, false)
require.False(t, ok)
v, ok := l.Get(key)
require.True(t, ok)
require.Equal(t, v1, v)
})
t.Run("Update - override", func(t *testing.T) {
ok := l.Add(key, v2, true)
require.True(t, ok)
v, ok := l.Get(key)
require.True(t, ok)
require.Equal(t, v2, v)
})
t.Run("Remove", func(t *testing.T) {
ok := l.Remove(key)
require.True(t, ok)
})
t.Run("Remove missing", func(t *testing.T) {
ok := l.Remove(key)
require.False(t, ok)
})
}
func Test_PlanLocals_Equal(t *testing.T) {
cmp := func(name string, a, b PlanLocals, expected bool) {
t.Run(name, func(t *testing.T) {
require.True(t, a.Equal(a))
require.True(t, b.Equal(b))
if expected {
require.True(t, a.Equal(b))
require.True(t, b.Equal(a))
} else {
require.False(t, a.Equal(b))
require.False(t, b.Equal(a))
}
})
}
cmp("Nil", nil, nil, true)
cmp("Nil & empty", nil, PlanLocals{}, true)
cmp("Empty", PlanLocals{}, PlanLocals{}, true)
cmp("Same keys & values", PlanLocals{
"key1": "v1",
}, PlanLocals{
"key1": "v1",
}, true)
cmp("Diff keys", PlanLocals{
"key2": "v1",
}, PlanLocals{
"key1": "v1",
}, false)
cmp("Same keys & diff values", PlanLocals{
"key1": "v1",
}, PlanLocals{
"key1": "v2",
}, false)
cmp("Same multi keys & values", PlanLocals{
"key1": "v1",
"ket2": "v2",
}, PlanLocals{
"key1": "v1",
"ket2": "v2",
}, true)
cmp("Same multi keys & values - reorder", PlanLocals{
"key1": "v1",
"ket2": "v2",
}, PlanLocals{
"ket2": "v2",
"key1": "v1",
}, true)
}

View file

@ -49,6 +49,13 @@ func (in *Action) DeepCopyInto(out *Action) {
(*out)[key] = val
}
}
if in.Locals != nil {
in, out := &in.Locals, &out.Locals
*out = make(PlanLocals, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
return
}
@ -1852,6 +1859,28 @@ func (in Plan) DeepCopy() Plan {
return *out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in PlanLocals) DeepCopyInto(out *PlanLocals) {
{
in := &in
*out = make(PlanLocals, len(*in))
for key, val := range *in {
(*out)[key] = val
}
return
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlanLocals.
func (in PlanLocals) DeepCopy() PlanLocals {
if in == nil {
return nil
}
out := new(PlanLocals)
in.DeepCopyInto(out)
return *out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *RocksDBEncryptionSpec) DeepCopyInto(out *RocksDBEncryptionSpec) {
*out = *in

View file

@ -26,9 +26,10 @@ import (
"sync"
"time"
"github.com/rs/zerolog"
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil/inspector/throttle"
"github.com/rs/zerolog"
)
func GetAllActions() []api.ActionType {

View file

@ -67,6 +67,8 @@ type ActionContext interface {
member.StateInspectorGetter
ActionLocalsContext
// GetMemberStatusByID returns the current member status
// for the member with given id.
// Returns member status, true when found, or false
@ -129,6 +131,13 @@ type ActionContext interface {
SelectImage(spec api.DeploymentSpec, status api.DeploymentStatus) (api.ImageInfo, bool)
}
type ActionLocalsContext interface {
CurrentLocals() api.PlanLocals
Get(action api.Action, key api.PlanLocalKey) (string, bool)
Add(key api.PlanLocalKey, value string, override bool) bool
}
// newActionContext creates a new ActionContext implementation.
func newActionContext(log zerolog.Logger, context Context, cachedStatus inspectorInterface.Inspector) ActionContext {
return &actionContext{
@ -143,6 +152,19 @@ type actionContext struct {
context Context
log zerolog.Logger
cachedStatus inspectorInterface.Inspector
locals api.PlanLocals
}
func (ac *actionContext) CurrentLocals() api.PlanLocals {
return ac.locals
}
func (ac *actionContext) Get(action api.Action, key api.PlanLocalKey) (string, bool) {
return ac.locals.GetWithParent(action.Locals, key)
}
func (ac *actionContext) Add(key api.PlanLocalKey, value string, override bool) bool {
return ac.locals.Add(key, value, override)
}
func (ac *actionContext) WithArangoMember(cache inspectorInterface.Inspector, timeout time.Duration, name string) reconciler.ArangoMemberModContext {

View file

@ -23,8 +23,9 @@ package reconcile
import (
"context"
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
"github.com/rs/zerolog"
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
)
type actionEmpty struct {

View file

@ -168,12 +168,16 @@ func (d *Reconciler) executePlan(ctx context.Context, cachedStatus inspectorInte
}
for k, v := range planAction.Params {
logContext = logContext.Str(k, v)
logContext = logContext.Str("param."+k, v)
}
for k, v := range planAction.Locals {
logContext = logContext.Str("local."+k.String(), v)
}
log := logContext.Logger()
action := d.createAction(log, planAction, cachedStatus)
action, actionContext := d.createAction(log, planAction, cachedStatus)
done, abort, recall, retry, err := d.executeAction(ctx, log, planAction, action)
if err != nil {
@ -247,6 +251,8 @@ func (d *Reconciler) executePlan(ctx context.Context, cachedStatus inspectorInte
plan[0].StartTime = &now
}
plan[0].Locals.Merge(actionContext.CurrentLocals())
return plan, recall, nil
}
}
@ -305,7 +311,7 @@ func (d *Reconciler) executeAction(ctx context.Context, log zerolog.Logger, plan
}
// createAction create action object based on action type
func (d *Reconciler) createAction(log zerolog.Logger, action api.Action, cachedStatus inspectorInterface.Inspector) Action {
func (d *Reconciler) createAction(log zerolog.Logger, action api.Action, cachedStatus inspectorInterface.Inspector) (Action, ActionContext) {
actionCtx := newActionContext(log.With().Str("id", action.ID).Str("type", action.Type.String()).Logger(), d.context, cachedStatus)
f, ok := getActionFactory(action.Type)
@ -313,5 +319,5 @@ func (d *Reconciler) createAction(log zerolog.Logger, action api.Action, cachedS
panic(fmt.Sprintf("Unknown action type '%s'", action.Type))
}
return f(log, action, actionCtx)
return f(log, action, actionCtx), actionCtx
}