github.com/fabianvf/ocp-release-operator-sdk@v0.0.0-20190426141702-57620ee2f090/cmd/operator-sdk/scorecard/test_definitions.go (about) 1 // Copyright 2019 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 scorecard 16 17 import ( 18 "context" 19 20 olmapiv1alpha1 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1" 21 v1 "k8s.io/api/core/v1" 22 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 23 "sigs.k8s.io/controller-runtime/pkg/client" 24 ) 25 26 // Type Definitions 27 28 // Test provides methods for running scorecard tests 29 type Test interface { 30 GetName() string 31 GetDescription() string 32 IsCumulative() bool 33 Run(context.Context) *TestResult 34 } 35 36 // TestResult contains a test's points, suggestions, and errors 37 type TestResult struct { 38 Test Test 39 EarnedPoints int 40 MaximumPoints int 41 Suggestions []string 42 Errors []error 43 } 44 45 // TestInfo contains information about the scorecard test 46 type TestInfo struct { 47 Name string 48 Description string 49 // If a test is set to cumulative, the scores of multiple runs of the same test on separate CRs are added together for the total score. 50 // If cumulative is false, if any test failed, the total score is 0/1. Otherwise 1/1. 51 Cumulative bool 52 } 53 54 // GetName return the test name 55 func (i TestInfo) GetName() string { return i.Name } 56 57 // GetDescription returns the test description 58 func (i TestInfo) GetDescription() string { return i.Description } 59 60 // IsCumulative returns true if the test's scores are intended to be cumulative 61 func (i TestInfo) IsCumulative() bool { return i.Cumulative } 62 63 // BasicTestConfig contains all variables required by the BasicTest TestSuite 64 type BasicTestConfig struct { 65 Client client.Client 66 CR *unstructured.Unstructured 67 ProxyPod *v1.Pod 68 } 69 70 // OLMTestConfig contains all variables required by the OLMTest TestSuite 71 type OLMTestConfig struct { 72 Client client.Client 73 CR *unstructured.Unstructured 74 CSV *olmapiv1alpha1.ClusterServiceVersion 75 CRDsDir string 76 ProxyPod *v1.Pod 77 } 78 79 // TestSuite contains a list of tests and results, along with the relative weights of each test 80 type TestSuite struct { 81 TestInfo 82 Tests []Test 83 TestResults []*TestResult 84 Weights map[string]float64 85 } 86 87 // Test definitions 88 89 // CheckSpecTest is a scorecard test that verifies that the CR has a spec block 90 type CheckSpecTest struct { 91 TestInfo 92 BasicTestConfig 93 } 94 95 // NewCheckSpecTest returns a new CheckSpecTest object 96 func NewCheckSpecTest(conf BasicTestConfig) *CheckSpecTest { 97 return &CheckSpecTest{ 98 BasicTestConfig: conf, 99 TestInfo: TestInfo{ 100 Name: "Spec Block Exists", 101 Description: "Custom Resource has a Spec Block", 102 Cumulative: false, 103 }, 104 } 105 } 106 107 // CheckStatusTest is a scorecard test that verifies that the CR has a status block 108 type CheckStatusTest struct { 109 TestInfo 110 BasicTestConfig 111 } 112 113 // NewCheckStatusTest returns a new CheckStatusTest object 114 func NewCheckStatusTest(conf BasicTestConfig) *CheckStatusTest { 115 return &CheckStatusTest{ 116 BasicTestConfig: conf, 117 TestInfo: TestInfo{ 118 Name: "Status Block Exists", 119 Description: "Custom Resource has a Status Block", 120 Cumulative: false, 121 }, 122 } 123 } 124 125 // WritingIntoCRsHasEffectTest is a scorecard test that verifies that the operator is making PUT and/or POST requests to the API server 126 type WritingIntoCRsHasEffectTest struct { 127 TestInfo 128 BasicTestConfig 129 } 130 131 // NewWritingIntoCRsHasEffectTest returns a new WritingIntoCRsHasEffectTest object 132 func NewWritingIntoCRsHasEffectTest(conf BasicTestConfig) *WritingIntoCRsHasEffectTest { 133 return &WritingIntoCRsHasEffectTest{ 134 BasicTestConfig: conf, 135 TestInfo: TestInfo{ 136 Name: "Writing into CRs has an effect", 137 Description: "A CR sends PUT/POST requests to the API server to modify resources in response to spec block changes", 138 Cumulative: false, 139 }, 140 } 141 } 142 143 // CRDsHaveValidationTest is a scorecard test that verifies that all CRDs have a validation section 144 type CRDsHaveValidationTest struct { 145 TestInfo 146 OLMTestConfig 147 } 148 149 // NewCRDsHaveValidationTest returns a new CRDsHaveValidationTest object 150 func NewCRDsHaveValidationTest(conf OLMTestConfig) *CRDsHaveValidationTest { 151 return &CRDsHaveValidationTest{ 152 OLMTestConfig: conf, 153 TestInfo: TestInfo{ 154 Name: "Provided APIs have validation", 155 Description: "All CRDs have an OpenAPI validation subsection", 156 Cumulative: true, 157 }, 158 } 159 } 160 161 // CRDsHaveResourcesTest is a scorecard test that verifies that the CSV lists used resources in its owned CRDs secyion 162 type CRDsHaveResourcesTest struct { 163 TestInfo 164 OLMTestConfig 165 } 166 167 // NewCRDsHaveResourcesTest returns a new CRDsHaveResourcesTest object 168 func NewCRDsHaveResourcesTest(conf OLMTestConfig) *CRDsHaveResourcesTest { 169 return &CRDsHaveResourcesTest{ 170 OLMTestConfig: conf, 171 TestInfo: TestInfo{ 172 Name: "Owned CRDs have resources listed", 173 Description: "All Owned CRDs contain a resources subsection", 174 Cumulative: true, 175 }, 176 } 177 } 178 179 // AnnotationsContainExamplesTest is a scorecard test that verifies that the CSV contains examples via the alm-examples annotation 180 type AnnotationsContainExamplesTest struct { 181 TestInfo 182 OLMTestConfig 183 } 184 185 // NewAnnotationsContainExamplesTest returns a new AnnotationsContainExamplesTest object 186 func NewAnnotationsContainExamplesTest(conf OLMTestConfig) *AnnotationsContainExamplesTest { 187 return &AnnotationsContainExamplesTest{ 188 OLMTestConfig: conf, 189 TestInfo: TestInfo{ 190 Name: "CRs have at least 1 example", 191 Description: "The CSV's metadata contains an alm-examples section", 192 Cumulative: true, 193 }, 194 } 195 } 196 197 // SpecDescriptorsTest is a scorecard test that verifies that all spec fields have descriptors 198 type SpecDescriptorsTest struct { 199 TestInfo 200 OLMTestConfig 201 } 202 203 // NewSpecDescriptorsTest returns a new SpecDescriptorsTest object 204 func NewSpecDescriptorsTest(conf OLMTestConfig) *SpecDescriptorsTest { 205 return &SpecDescriptorsTest{ 206 OLMTestConfig: conf, 207 TestInfo: TestInfo{ 208 Name: "Spec fields with descriptors", 209 Description: "All spec fields have matching descriptors in the CSV", 210 Cumulative: true, 211 }, 212 } 213 } 214 215 // StatusDescriptorsTest is a scorecard test that verifies that all status fields have descriptors 216 type StatusDescriptorsTest struct { 217 TestInfo 218 OLMTestConfig 219 } 220 221 // NewStatusDescriptorsTest returns a new StatusDescriptorsTest object 222 func NewStatusDescriptorsTest(conf OLMTestConfig) *StatusDescriptorsTest { 223 return &StatusDescriptorsTest{ 224 OLMTestConfig: conf, 225 TestInfo: TestInfo{ 226 Name: "Status fields with descriptors", 227 Description: "All status fields have matching descriptors in the CSV", 228 Cumulative: true, 229 }, 230 } 231 } 232 233 // Test Suite Declarations 234 235 // NewBasicTestSuite returns a new TestSuite object containing basic, functional operator tests 236 func NewBasicTestSuite(conf BasicTestConfig) *TestSuite { 237 ts := NewTestSuite( 238 "Basic Tests", 239 "Test suite that runs basic, functional operator tests", 240 ) 241 ts.AddTest(NewCheckSpecTest(conf), 1.5) 242 ts.AddTest(NewCheckStatusTest(conf), 1) 243 ts.AddTest(NewWritingIntoCRsHasEffectTest(conf), 1) 244 245 return ts 246 } 247 248 // NewOLMTestSuite returns a new TestSuite object containing CSV best practice checks 249 func NewOLMTestSuite(conf OLMTestConfig) *TestSuite { 250 ts := NewTestSuite( 251 "OLM Tests", 252 "Test suite checks if an operator's CSV follows best practices", 253 ) 254 255 ts.AddTest(NewCRDsHaveValidationTest(conf), 1.25) 256 ts.AddTest(NewCRDsHaveResourcesTest(conf), 1) 257 ts.AddTest(NewAnnotationsContainExamplesTest(conf), 1) 258 ts.AddTest(NewSpecDescriptorsTest(conf), 1) 259 ts.AddTest(NewStatusDescriptorsTest(conf), 1) 260 261 return ts 262 } 263 264 // Helper functions 265 266 // ResultsPassFail will be used when multiple CRs are supported 267 func ResultsPassFail(results []TestResult) (earned, max int) { 268 for _, result := range results { 269 if result.EarnedPoints != result.MaximumPoints { 270 return 0, 1 271 } 272 } 273 return 1, 1 274 } 275 276 // ResultsCumulative will be used when multiple CRs are supported 277 func ResultsCumulative(results []TestResult) (earned, max int) { 278 for _, result := range results { 279 earned += result.EarnedPoints 280 max += result.MaximumPoints 281 } 282 return earned, max 283 } 284 285 // AddTest adds a new Test to a TestSuite along with a relative weight for the new Test 286 func (ts *TestSuite) AddTest(t Test, weight float64) { 287 ts.Tests = append(ts.Tests, t) 288 ts.Weights[t.GetName()] = weight 289 } 290 291 // TotalScore calculates and returns the total score of all run Tests in a TestSuite 292 func (ts *TestSuite) TotalScore() (score int) { 293 floatScore := 0.0 294 for _, result := range ts.TestResults { 295 if result.MaximumPoints != 0 { 296 floatScore += (float64(result.EarnedPoints) / float64(result.MaximumPoints)) * ts.Weights[result.Test.GetName()] 297 } 298 } 299 // scale to a percentage 300 addedWeights := 0.0 301 for _, weight := range ts.Weights { 302 addedWeights += weight 303 } 304 floatScore = floatScore * (100 / addedWeights) 305 return int(floatScore) 306 } 307 308 // Run runs all Tests in a TestSuite 309 func (ts *TestSuite) Run(ctx context.Context) { 310 for _, test := range ts.Tests { 311 ts.TestResults = append(ts.TestResults, test.Run(ctx)) 312 } 313 } 314 315 // NewTestSuite returns a new TestSuite with a given name and description 316 func NewTestSuite(name, description string) *TestSuite { 317 return &TestSuite{ 318 TestInfo: TestInfo{ 319 Name: name, 320 Description: description, 321 }, 322 Weights: make(map[string]float64), 323 } 324 }