Skip to content

Commit c261803

Browse files
author
Shawn Hurley
authored
Adding ability to specify requeue period per GVK via watches file. (#641)
**Description of the change:** Adding ability to specify GVK specific re-queue times via the watches file **Motivation for the change:** * Not every GVK should be handled the same * wanted to add some test cases.
1 parent f9d0f00 commit c261803

File tree

13 files changed

+304
-17
lines changed

13 files changed

+304
-17
lines changed

commands/operator-sdk/cmd/up/local.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"runtime"
2626
"strings"
2727
"syscall"
28+
"time"
2829

2930
"github.com/operator-framework/operator-sdk/commands/operator-sdk/cmd/cmdutil"
3031
ansibleOperator "github.com/operator-framework/operator-sdk/pkg/ansible/operator"
@@ -142,7 +143,7 @@ func upLocalAnsible() {
142143
}
143144

144145
// start the operator
145-
go ansibleOperator.Run(done, mgr, "./watches.yaml")
146+
go ansibleOperator.Run(done, mgr, "./watches.yaml", time.Minute)
146147

147148
// wait for either to finish
148149
err = <-done

pkg/ansible/operator/operator.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import (
2929
// Run - A blocking function which starts a controller-runtime manager
3030
// It starts an Operator by reading in the values in `./watches.yaml`, adds a controller
3131
// to the manager, and finally running the manager.
32-
func Run(done chan error, mgr manager.Manager, watchesPath string) {
32+
func Run(done chan error, mgr manager.Manager, watchesPath string, reconcilePeriod time.Duration) {
3333
watches, err := runner.NewFromWatches(watchesPath)
3434
if err != nil {
3535
logrus.Error("Failed to get watches")
@@ -40,10 +40,16 @@ func Run(done chan error, mgr manager.Manager, watchesPath string) {
4040
c := signals.SetupSignalHandler()
4141

4242
for gvk, runner := range watches {
43-
controller.Add(mgr, controller.Options{
44-
GVK: gvk,
45-
Runner: runner,
46-
})
43+
o := controller.Options{
44+
GVK: gvk,
45+
Runner: runner,
46+
ReconcilePeriod: reconcilePeriod,
47+
}
48+
d, ok := runner.GetReconcilePeriod()
49+
if ok {
50+
o.ReconcilePeriod = d
51+
}
52+
controller.Add(mgr, o)
4753
}
4854
done <- mgr.Start(c)
4955
}

pkg/ansible/runner/runner.go

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"path/filepath"
2626
"strconv"
2727
"strings"
28+
"time"
2829

2930
"github.com/operator-framework/operator-sdk/pkg/ansible/paramconv"
3031
"github.com/operator-framework/operator-sdk/pkg/ansible/runner/eventapi"
@@ -40,17 +41,19 @@ import (
4041
type Runner interface {
4142
Run(*unstructured.Unstructured, string) (chan eventapi.JobEvent, error)
4243
GetFinalizer() (string, bool)
44+
GetReconcilePeriod() (time.Duration, bool)
4345
}
4446

4547
// watch holds data used to create a mapping of GVK to ansible playbook or role.
4648
// The mapping is used to compose an ansible operator.
4749
type watch struct {
48-
Version string `yaml:"version"`
49-
Group string `yaml:"group"`
50-
Kind string `yaml:"kind"`
51-
Playbook string `yaml:"playbook"`
52-
Role string `yaml:"role"`
53-
Finalizer *Finalizer `yaml:"finalizer"`
50+
Version string `yaml:"version"`
51+
Group string `yaml:"group"`
52+
Kind string `yaml:"kind"`
53+
Playbook string `yaml:"playbook"`
54+
Role string `yaml:"role"`
55+
ReconcilePeriod string `yaml:"reconcilePeriod"`
56+
Finalizer *Finalizer `yaml:"finalizer"`
5457
}
5558

5659
// Finalizer - Expose finalizer to be used by a user.
@@ -82,19 +85,28 @@ func NewFromWatches(path string) (map[schema.GroupVersionKind]Runner, error) {
8285
Version: w.Version,
8386
Kind: w.Kind,
8487
}
88+
var reconcilePeriod time.Duration
89+
if w.ReconcilePeriod != "" {
90+
d, err := time.ParseDuration(w.ReconcilePeriod)
91+
if err != nil {
92+
return nil, fmt.Errorf("unable to parse duration: %v - %v, setting to default", w.ReconcilePeriod, err)
93+
}
94+
reconcilePeriod = d
95+
}
96+
8597
// Check if schema is a duplicate
8698
if _, ok := m[s]; ok {
8799
return nil, fmt.Errorf("duplicate GVK: %v", s.String())
88100
}
89101
switch {
90102
case w.Playbook != "":
91-
r, err := NewForPlaybook(w.Playbook, s, w.Finalizer)
103+
r, err := NewForPlaybook(w.Playbook, s, w.Finalizer, reconcilePeriod)
92104
if err != nil {
93105
return nil, err
94106
}
95107
m[s] = r
96108
case w.Role != "":
97-
r, err := NewForRole(w.Role, s, w.Finalizer)
109+
r, err := NewForRole(w.Role, s, w.Finalizer, reconcilePeriod)
98110
if err != nil {
99111
return nil, err
100112
}
@@ -107,7 +119,7 @@ func NewFromWatches(path string) (map[schema.GroupVersionKind]Runner, error) {
107119
}
108120

109121
// NewForPlaybook returns a new Runner based on the path to an ansible playbook.
110-
func NewForPlaybook(path string, gvk schema.GroupVersionKind, finalizer *Finalizer) (Runner, error) {
122+
func NewForPlaybook(path string, gvk schema.GroupVersionKind, finalizer *Finalizer, reconcilePeriod time.Duration) (Runner, error) {
111123
if !filepath.IsAbs(path) {
112124
return nil, fmt.Errorf("playbook path must be absolute for %v", gvk)
113125
}
@@ -117,6 +129,7 @@ func NewForPlaybook(path string, gvk schema.GroupVersionKind, finalizer *Finaliz
117129
cmdFunc: func(ident, inputDirPath string) *exec.Cmd {
118130
return exec.Command("ansible-runner", "-vv", "-p", path, "-i", ident, "run", inputDirPath)
119131
},
132+
reconcilePeriod: reconcilePeriod,
120133
}
121134
err := r.addFinalizer(finalizer)
122135
if err != nil {
@@ -126,7 +139,7 @@ func NewForPlaybook(path string, gvk schema.GroupVersionKind, finalizer *Finaliz
126139
}
127140

128141
// NewForRole returns a new Runner based on the path to an ansible role.
129-
func NewForRole(path string, gvk schema.GroupVersionKind, finalizer *Finalizer) (Runner, error) {
142+
func NewForRole(path string, gvk schema.GroupVersionKind, finalizer *Finalizer, reconcilePeriod time.Duration) (Runner, error) {
130143
if !filepath.IsAbs(path) {
131144
return nil, fmt.Errorf("role path must be absolute for %v", gvk)
132145
}
@@ -138,6 +151,7 @@ func NewForRole(path string, gvk schema.GroupVersionKind, finalizer *Finalizer)
138151
rolePath, roleName := filepath.Split(path)
139152
return exec.Command("ansible-runner", "-vv", "--role", roleName, "--roles-path", rolePath, "--hosts", "localhost", "-i", ident, "run", inputDirPath)
140153
},
154+
reconcilePeriod: reconcilePeriod,
141155
}
142156
err := r.addFinalizer(finalizer)
143157
if err != nil {
@@ -153,6 +167,7 @@ type runner struct {
153167
Finalizer *Finalizer
154168
cmdFunc func(ident, inputDirPath string) *exec.Cmd // returns a Cmd that runs ansible-runner
155169
finalizerCmdFunc func(ident, inputDirPath string) *exec.Cmd
170+
reconcilePeriod time.Duration
156171
}
157172

158173
func (r *runner) Run(u *unstructured.Unstructured, kubeconfig string) (chan eventapi.JobEvent, error) {
@@ -224,6 +239,14 @@ func (r *runner) Run(u *unstructured.Unstructured, kubeconfig string) (chan even
224239
return receiver.Events, nil
225240
}
226241

242+
// GetReconcilePeriod - new reconcile period.
243+
func (r *runner) GetReconcilePeriod() (time.Duration, bool) {
244+
if r.reconcilePeriod == time.Duration(0) {
245+
return r.reconcilePeriod, false
246+
}
247+
return r.reconcilePeriod, true
248+
}
249+
227250
func (r *runner) GetFinalizer() (string, bool) {
228251
if r.Finalizer != nil {
229252
return r.Finalizer.Name, true

pkg/ansible/runner/runner_test.go

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
// Copyright 2018 The Operator-SDK Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package runner
16+
17+
import (
18+
"reflect"
19+
"testing"
20+
"time"
21+
22+
"k8s.io/apimachinery/pkg/runtime/schema"
23+
)
24+
25+
func TestNewFromWatches(t *testing.T) {
26+
testCases := []struct {
27+
name string
28+
path string
29+
expectedMap map[schema.GroupVersionKind]runner
30+
shouldError bool
31+
}{
32+
{
33+
name: "error duplicate GVK",
34+
path: "testdata/duplicate_gvk.yaml",
35+
shouldError: true,
36+
},
37+
{
38+
name: "error no file",
39+
path: "testdata/please_don't_create_me_gvk.yaml",
40+
shouldError: true,
41+
},
42+
{
43+
name: "error invalid yaml",
44+
path: "testdata/invalid.yaml",
45+
shouldError: true,
46+
},
47+
{
48+
name: "error invalid playbook path",
49+
path: "testdata/invalid_playbook_path.yaml",
50+
shouldError: true,
51+
},
52+
{
53+
name: "error invalid playbook finalizer path",
54+
path: "testdata/invalid_finalizer_playbook_path.yaml",
55+
shouldError: true,
56+
},
57+
{
58+
name: "error invalid role path",
59+
path: "testdata/invalid_role_path.yaml",
60+
shouldError: true,
61+
},
62+
{
63+
name: "error invalid role finalizer path",
64+
path: "testdata/invalid_finalizer_role_path.yaml",
65+
shouldError: true,
66+
},
67+
{
68+
name: "error invalid duration",
69+
path: "testdata/invalid_duration.yaml",
70+
shouldError: true,
71+
},
72+
{
73+
name: "valid watches file",
74+
path: "testdata/valid.yaml",
75+
expectedMap: map[schema.GroupVersionKind]runner{
76+
schema.GroupVersionKind{
77+
Version: "v1alpha1",
78+
Group: "app.example.com",
79+
Kind: "NoFinalizer",
80+
}: runner{
81+
GVK: schema.GroupVersionKind{
82+
Version: "v1alpha1",
83+
Group: "app.example.com",
84+
Kind: "NoFinalizer",
85+
},
86+
Path: "/opt/ansible/playbook.yaml",
87+
reconcilePeriod: time.Second * 2,
88+
},
89+
schema.GroupVersionKind{
90+
Version: "v1alpha1",
91+
Group: "app.example.com",
92+
Kind: "Playbook",
93+
}: runner{
94+
GVK: schema.GroupVersionKind{
95+
Version: "v1alpha1",
96+
Group: "app.example.com",
97+
Kind: "Playbook",
98+
},
99+
Path: "/opt/ansible/playbook.yaml",
100+
Finalizer: &Finalizer{
101+
Name: "finalizer.app.example.com",
102+
Role: "/opt/ansible/role",
103+
Vars: map[string]interface{}{"sentinel": "finalizer_running"},
104+
},
105+
},
106+
schema.GroupVersionKind{
107+
Version: "v1alpha1",
108+
Group: "app.example.com",
109+
Kind: "Role",
110+
}: runner{
111+
GVK: schema.GroupVersionKind{
112+
Version: "v1alpha1",
113+
Group: "app.example.com",
114+
Kind: "Role",
115+
},
116+
Path: "/opt/ansible/role",
117+
Finalizer: &Finalizer{
118+
Name: "finalizer.app.example.com",
119+
Playbook: "/opt/ansible/playbook.yaml",
120+
Vars: map[string]interface{}{"sentinel": "finalizer_running"},
121+
},
122+
},
123+
},
124+
},
125+
}
126+
127+
for _, tc := range testCases {
128+
t.Run(tc.name, func(t *testing.T) {
129+
m, err := NewFromWatches(tc.path)
130+
if err != nil && !tc.shouldError {
131+
t.Fatalf("err: %v occurred unexpectedly", err)
132+
}
133+
if err != nil && tc.shouldError {
134+
return
135+
}
136+
for k, expectedR := range tc.expectedMap {
137+
r, ok := m[k]
138+
if !ok {
139+
t.Fatalf("did not find expected GVK: %v", k)
140+
}
141+
run, ok := r.(*runner)
142+
if !ok {
143+
t.Fatalf("here: %#v", r)
144+
}
145+
if run.Path != expectedR.Path {
146+
t.Fatalf("the GVK: %v unexpected path: %v expected path: %v", k, run.Path, expectedR.Path)
147+
}
148+
if run.GVK != expectedR.GVK {
149+
t.Fatalf("the GVK: %v\nunexpected GVK: %#v\nexpected GVK: %#v", k, run.GVK, expectedR.GVK)
150+
}
151+
if run.Finalizer != expectedR.Finalizer {
152+
if run.Finalizer.Name != expectedR.Finalizer.Name || run.Finalizer.Playbook != expectedR.Finalizer.Playbook || run.Finalizer.Role != expectedR.Finalizer.Role || reflect.DeepEqual(run.Finalizer.Vars["sentinel"], expectedR.Finalizer.Vars["sentininel"]) {
153+
t.Fatalf("the GVK: %v\nunexpected finalizer: %#v\nexpected finalizer: %#v", k, run.Finalizer, expectedR.Finalizer)
154+
}
155+
}
156+
if run.reconcilePeriod != expectedR.reconcilePeriod {
157+
t.Fatalf("the GVK: %v unexpected reconcile period: %v expected reconcile period: %v", k, run.reconcilePeriod, expectedR.reconcilePeriod)
158+
}
159+
}
160+
})
161+
}
162+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
- version: v1alpha1
3+
group: app.example.com
4+
kind: Database
5+
playbook: /opt/ansible/playbook.yaml
6+
finalizer:
7+
name: finalizer.app.example.com
8+
vars:
9+
sentinel: finalizer_running
10+
- version: v1alpha1
11+
group: app.example.com
12+
kind: Database
13+
playbook: /opt/ansible/playbook.yaml
14+
finalizer:
15+
name: finalizer.app.example.com
16+
vars:
17+
sentinel: finalizer_running
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
version: v1alpha1
3+
group: app.example.com
4+
kind: Database
5+
playbook: /opt/ansible/playbook.yaml
6+
finalizer:
7+
name: finalizer.app.example.com
8+
vars:
9+
sentinel: finalizer_running
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
- version: v1alpha1
3+
group: app.example.com
4+
kind: Database
5+
playbook: /opt/ansible/playbook.yaml
6+
reconcilePeriod: invalid
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
- version: v1alpha1
3+
group: app.example.com
4+
kind: Database
5+
playbook: /opt/ansible/playbook.yaml
6+
finalizer:
7+
name: finalizer.app.example.com
8+
playbook: opt/ansible/playbook.yaml
9+
vars:
10+
sentinel: finalizer_running
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
- version: v1alpha1
3+
group: app.example.com
4+
kind: Database
5+
playbook: /ansible/playbook.yaml
6+
finalizer:
7+
name: finalizer.app.example.com
8+
role: ansible/role
9+
vars:
10+
sentinel: finalizer_running
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
- version: v1alpha1
3+
group: app.example.com
4+
kind: Database
5+
playbook: opt/ansible/playbook.yaml
6+
finalizer:
7+
name: finalizer.app.example.com
8+
vars:
9+
sentinel: finalizer_running

0 commit comments

Comments
 (0)