@@ -18,155 +18,52 @@ import (
18
18
"context"
19
19
"encoding/json"
20
20
"fmt"
21
- "math/rand"
22
- "reflect"
23
21
"strings"
24
- "time"
25
22
26
- "github.com/spf13/viper"
27
- "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
28
23
"k8s.io/apimachinery/pkg/types"
29
- "k8s.io/apimachinery/pkg/util/wait"
30
- "sigs.k8s.io/controller-runtime/pkg/client"
31
24
)
32
25
33
- // checkSpecAndStat checks that the spec and status blocks exist. If noStore is set to true, this function
34
- // will not store the result of the test in scTests and will instead just wait until the spec and
35
- // status blocks exist or return an error after the timeout.
36
- func checkSpecAndStat (runtimeClient client.Client , obj * unstructured.Unstructured , noStore bool ) error {
37
- testSpec := scorecardTest {testType : basicOperator , name : "Spec Block Exists" , maximumPoints : 1 }
38
- testStat := scorecardTest {testType : basicOperator , name : "Status Block Exists" , maximumPoints : 1 }
39
- err := wait .Poll (time .Second * 1 , time .Second * time .Duration (viper .GetInt64 (InitTimeoutOpt )), func () (bool , error ) {
40
- err := runtimeClient .Get (context .TODO (), types.NamespacedName {Namespace : obj .GetNamespace (), Name : obj .GetName ()}, obj )
41
- if err != nil {
42
- return false , fmt .Errorf ("error getting custom resource: %v" , err )
43
- }
44
- var specPass , statusPass bool
45
- if obj .Object ["spec" ] != nil {
46
- testSpec .earnedPoints = 1
47
- specPass = true
48
- }
49
-
50
- if obj .Object ["status" ] != nil {
51
- testStat .earnedPoints = 1
52
- statusPass = true
53
- }
54
- return statusPass && specPass , nil
55
- })
56
- if ! noStore {
57
- scTests = append (scTests , testSpec , testStat )
58
- }
59
- if err != nil && err != wait .ErrWaitTimeout {
60
- return err
26
+ // Run - implements Test interface
27
+ func (t * CheckSpecTest ) Run (ctx context.Context ) * TestResult {
28
+ res := & TestResult {Test : t , MaximumPoints : 1 }
29
+ err := t .Client .Get (ctx , types.NamespacedName {Namespace : t .CR .GetNamespace (), Name : t .CR .GetName ()}, t .CR )
30
+ if err != nil {
31
+ res .Errors = append (res .Errors , fmt .Errorf ("error getting custom resource: %v" , err ))
32
+ return res
61
33
}
62
- if testSpec . earnedPoints != 1 {
63
- scSuggestions = append ( scSuggestions , "Add a 'spec' field to your Custom Resource" )
34
+ if t . CR . Object [ "spec" ] != nil {
35
+ res . EarnedPoints ++
64
36
}
65
- if testStat . earnedPoints != 1 {
66
- scSuggestions = append (scSuggestions , "Add a 'status ' field to your Custom Resource" )
37
+ if res . EarnedPoints != 1 {
38
+ res . Suggestions = append (res . Suggestions , "Add a 'spec ' field to your Custom Resource" )
67
39
}
68
- return nil
40
+ return res
69
41
}
70
42
71
- // TODO: user specified tests for operators
72
-
73
- // checkStatusUpdate looks at all fields in the spec section of a custom resource and attempts to modify them and
74
- // see if the status changes as a result. This is a bit prone to breakage as this is a black box test and we don't
75
- // know much about how the operators we are testing actually work and may pass an invalid value. In the future, we
76
- // should use user-specified tests
77
- func checkStatusUpdate (runtimeClient client.Client , obj * unstructured.Unstructured ) error {
78
- test := scorecardTest {testType : basicOperator , name : "Operator actions are reflected in status" , maximumPoints : 1 }
79
- err := runtimeClient .Get (context .TODO (), types.NamespacedName {Namespace : obj .GetNamespace (), Name : obj .GetName ()}, obj )
43
+ // Run - implements Test interface
44
+ func (t * CheckStatusTest ) Run (ctx context.Context ) * TestResult {
45
+ res := & TestResult {Test : t , MaximumPoints : 1 }
46
+ err := t .Client .Get (ctx , types.NamespacedName {Namespace : t .CR .GetNamespace (), Name : t .CR .GetName ()}, t .CR )
80
47
if err != nil {
81
- return fmt .Errorf ("error getting custom resource: %v" , err )
48
+ res .Errors = append (res .Errors , fmt .Errorf ("error getting custom resource: %v" , err ))
49
+ return res
82
50
}
83
- if obj .Object ["status" ] == nil || obj .Object ["spec" ] == nil {
84
- scTests = append (scTests , test )
85
- return nil
51
+ if t .CR .Object ["status" ] != nil {
52
+ res .EarnedPoints ++
86
53
}
87
- statCopy := make (map [string ]interface {})
88
- for k , v := range obj .Object ["status" ].(map [string ]interface {}) {
89
- statCopy [k ] = v
90
- }
91
- specMap := obj .Object ["spec" ].(map [string ]interface {})
92
- err = modifySpecAndCheck (specMap , obj )
93
- if err != nil {
94
- test .earnedPoints = 0
95
- scSuggestions = append (scSuggestions , "Make sure that the 'status' block is always updated to reflect changes after the 'spec' block is changed" )
96
- scTests = append (scTests , test )
97
- return nil
98
- }
99
- test .earnedPoints = 1
100
- scTests = append (scTests , test )
101
- return nil
102
- }
103
-
104
- // modifySpecAndCheck is a helper function for checkStatusUpdate
105
- func modifySpecAndCheck (specMap map [string ]interface {}, obj * unstructured.Unstructured ) error {
106
- statCopy := make (map [string ]interface {})
107
- for k , v := range obj .Object ["status" ].(map [string ]interface {}) {
108
- statCopy [k ] = v
109
- }
110
- var err error
111
- for k , v := range specMap {
112
- mapType := false
113
- switch t := v .(type ) {
114
- case int64 :
115
- specMap [k ] = specMap [k ].(int64 ) + 1
116
- case float64 :
117
- specMap [k ] = specMap [k ].(float64 ) + 1
118
- case string :
119
- // TODO: try and find out how to make this better
120
- // Since strings may be very operator specific, this test may not work correctly in many cases
121
- specMap [k ] = fmt .Sprintf ("operator sdk test value %f" , rand .Float64 ())
122
- case bool :
123
- specMap [k ] = ! specMap [k ].(bool )
124
- case map [string ]interface {}:
125
- mapType = true
126
- err = modifySpecAndCheck (specMap [k ].(map [string ]interface {}), obj )
127
- case []map [string ]interface {}:
128
- mapType = true
129
- for _ , item := range specMap [k ].([]map [string ]interface {}) {
130
- err = modifySpecAndCheck (item , obj )
131
- if err != nil {
132
- break
133
- }
134
- }
135
- case []interface {}: // TODO: Decide how this should be handled
136
- default :
137
- fmt .Printf ("Unknown type for key (%s) in spec: (%v)\n " , k , reflect .TypeOf (t ))
138
- }
139
- if ! mapType {
140
- if err := runtimeClient .Update (context .TODO (), obj ); err != nil {
141
- return fmt .Errorf ("failed to update object: %v" , err )
142
- }
143
- err = wait .Poll (time .Second * 1 , time .Second * 15 , func () (done bool , err error ) {
144
- err = runtimeClient .Get (context .TODO (), types.NamespacedName {Namespace : obj .GetNamespace (), Name : obj .GetName ()}, obj )
145
- if err != nil {
146
- return false , err
147
- }
148
- return ! reflect .DeepEqual (statCopy , obj .Object ["status" ]), nil
149
- })
150
- }
151
- if err != nil {
152
- return err
153
- }
154
- //reset stat copy to match
155
- statCopy = make (map [string ]interface {})
156
- for k , v := range obj .Object ["status" ].(map [string ]interface {}) {
157
- statCopy [k ] = v
158
- }
54
+ if res .EarnedPoints != 1 {
55
+ res .Suggestions = append (res .Suggestions , "Add a 'status' field to your Custom Resource" )
159
56
}
160
- return nil
57
+ return res
161
58
}
162
59
163
- // wiritingIntoCRsHasEffect simply looks at the proxy logs and verifies that the operator is sending PUT
164
- // and/or POST requests to the API server, which should mean that it is creating or modifying resources.
165
- func writingIntoCRsHasEffect (obj * unstructured.Unstructured ) (string , error ) {
166
- test := scorecardTest {testType : basicOperator , name : "Writing into CRs has an effect" , maximumPoints : 1 }
167
- logs , err := getProxyLogs ()
60
+ // Run - implements Test interface
61
+ func (t * WritingIntoCRsHasEffectTest ) Run (ctx context.Context ) * TestResult {
62
+ res := & TestResult {Test : t , MaximumPoints : 1 }
63
+ logs , err := getProxyLogs (t .ProxyPod )
168
64
if err != nil {
169
- return "" , err
65
+ res .Errors = append (res .Errors , fmt .Errorf ("error getting proxy logs: %v" , err ))
66
+ return res
170
67
}
171
68
msgMap := make (map [string ]interface {})
172
69
for _ , msg := range strings .Split (logs , "\n " ) {
@@ -178,13 +75,12 @@ func writingIntoCRsHasEffect(obj *unstructured.Unstructured) (string, error) {
178
75
continue
179
76
}
180
77
if method == "PUT" || method == "POST" {
181
- test . earnedPoints = 1
78
+ res . EarnedPoints = 1
182
79
break
183
80
}
184
81
}
185
- scTests = append (scTests , test )
186
- if test .earnedPoints != 1 {
187
- scSuggestions = append (scSuggestions , "The operator should write into objects to update state. No PUT or POST requests from you operator were recorded by the scorecard." )
82
+ if res .EarnedPoints != 1 {
83
+ res .Suggestions = append (res .Suggestions , "The operator should write into objects to update state. No PUT or POST requests from the operator were recorded by the scorecard." )
188
84
}
189
- return logs , nil
85
+ return res
190
86
}
0 commit comments