github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/testgrid/cmd/configurator/config_test.go (about) 1 /* 2 Copyright 2016 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package main 18 19 import ( 20 "fmt" 21 "io/ioutil" 22 "os" 23 "strings" 24 "testing" 25 26 "path/filepath" 27 28 "k8s.io/apimachinery/pkg/util/sets" 29 30 prow_config "k8s.io/test-infra/prow/config" 31 config_pb "k8s.io/test-infra/testgrid/config" 32 ) 33 34 type SQConfig struct { 35 Data map[string]string `yaml:"data,omitempty"` 36 } 37 38 var ( 39 companies = []string{ 40 "canonical", 41 "cos", 42 "cri-o", 43 "istio", 44 "google", 45 "kopeio", 46 "redhat", 47 "vmware", 48 } 49 orgs = []string{ 50 "conformance", 51 "presubmits", 52 "sig", 53 "wg", 54 } 55 prefixes = [][]string{orgs, companies} 56 ) 57 58 // Shared testgrid config, loaded at TestMain. 59 var cfg *config_pb.Configuration 60 61 func TestMain(m *testing.M) { 62 //make sure we can parse config.yaml 63 yamlData, err := ioutil.ReadFile("../../config.yaml") 64 if err != nil { 65 fmt.Printf("IO Error : Cannot Open File config.yaml") 66 os.Exit(1) 67 } 68 69 c := Config{} 70 if err := c.Update(yamlData); err != nil { 71 fmt.Printf("Yaml2Proto - Conversion Error %v", err) 72 os.Exit(1) 73 } 74 75 cfg, err = c.Raw() 76 if err != nil { 77 fmt.Printf("Error validating config: %v", err) 78 os.Exit(1) 79 } 80 81 os.Exit(m.Run()) 82 } 83 84 func TestConfig(t *testing.T) { 85 // testgroup - occurrence map, validate testgroups 86 testgroupMap := make(map[string]int32) 87 88 for testgroupidx, testgroup := range cfg.TestGroups { 89 // All testgroup must have a name and a query 90 if testgroup.Name == "" || testgroup.GcsPrefix == "" { 91 t.Errorf("Testgroup #%v (Name: '%v', Query: '%v'): - Must have a name and query", 92 testgroupidx, testgroup.Name, testgroup.GcsPrefix) 93 } 94 95 // All testgroup must not have duplicated names 96 if testgroupMap[testgroup.Name] > 0 { 97 t.Errorf("Duplicated Testgroup: %v", testgroup.Name) 98 } else { 99 testgroupMap[testgroup.Name] = 1 100 } 101 102 if !testgroup.IsExternal { 103 t.Errorf("Testgroup %v: IsExternal should always be true!", testgroup.Name) 104 } 105 if !testgroup.UseKubernetesClient { 106 t.Errorf("Testgroup %v: UseKubernetesClient should always be true!", testgroup.Name) 107 } 108 109 if strings.HasPrefix(testgroup.GcsPrefix, "kubernetes-jenkins/logs/") { 110 // The expectation is that testgroup.Name is the name of a Prow job and the GCSPrefix 111 // follows the convention kubernetes-jenkins/logs/.../jobName 112 // The final part of the prefix should be the job name. 113 expected := filepath.Join(filepath.Dir(testgroup.GcsPrefix), testgroup.Name) 114 if expected != testgroup.GcsPrefix { 115 t.Errorf("Kubernetes Testgroup %v GcsPrefix; Got %v; Want %v", testgroup.Name, testgroup.GcsPrefix, expected) 116 } 117 } 118 119 if testgroup.TestNameConfig != nil { 120 if testgroup.TestNameConfig.NameFormat == "" { 121 t.Errorf("Testgroup %v: NameFormat must not be empty!", testgroup.Name) 122 } 123 124 if len(testgroup.TestNameConfig.NameElements) != strings.Count(testgroup.TestNameConfig.NameFormat, "%") { 125 t.Errorf("Testgroup %v: TestNameConfig must have number NameElement equal to format count in NameFormat!", testgroup.Name) 126 } 127 } 128 129 // All PR testgroup has num_columns_recent equals 20 130 if strings.HasPrefix(testgroup.GcsPrefix, "kubernetes-jenkins/pr-logs/directory/") { 131 if testgroup.NumColumnsRecent < 20 { 132 t.Errorf("Testgroup %v: num_columns_recent: must be greater than 20 for presubmit jobs!", testgroup.Name) 133 } 134 } 135 } 136 137 // dashboard name set 138 dashboardmap := make(map[string]bool) 139 140 for dashboardidx, dashboard := range cfg.Dashboards { 141 // All dashboard must have a name 142 if dashboard.Name == "" { 143 t.Errorf("Dashboard %v: - Must have a name", dashboardidx) 144 } 145 146 found := false 147 for _, kind := range prefixes { 148 for _, prefix := range kind { 149 if strings.HasPrefix(dashboard.Name, prefix+"-") || dashboard.Name == prefix { 150 found = true 151 break 152 } 153 } 154 if found { 155 break 156 } 157 } 158 if !found { 159 t.Errorf("Dashboard %v: must prefix with one of: %v", dashboard.Name, prefixes) 160 } 161 162 // All dashboard must not have duplicated names 163 if dashboardmap[dashboard.Name] { 164 t.Errorf("Duplicated dashboard: %v", dashboard.Name) 165 } else { 166 dashboardmap[dashboard.Name] = true 167 } 168 169 // All dashboard must have at least one tab 170 if len(dashboard.DashboardTab) == 0 { 171 t.Errorf("Dashboard %v: - Must have more than one dashboardtab", dashboard.Name) 172 } 173 174 // dashboardtabSet is a set that checks duplicate tab name within each dashboard 175 dashboardtabSet := sets.NewString() 176 177 // dashboardtestgroupSet is a set that checks duplicate testgroups within each dashboard 178 dashboardtestgroupSet := sets.NewString() 179 180 // All notifications in dashboard must have a summary 181 if len(dashboard.Notifications) != 0 { 182 for notificationindex, notification := range dashboard.Notifications { 183 if notification.Summary == "" { 184 t.Errorf("Notification %v in dashboard %v: - Must have a summary", notificationindex, dashboard.Name) 185 } 186 } 187 } 188 189 for tabindex, dashboardtab := range dashboard.DashboardTab { 190 191 // All dashboardtab must have a name and a testgroup 192 if dashboardtab.Name == "" || dashboardtab.TestGroupName == "" { 193 t.Errorf("Dashboard %v, tab %v: - Must have a name and a testgroup name", dashboard.Name, tabindex) 194 } 195 196 // All dashboardtab within a dashboard must not have duplicated names 197 if dashboardtabSet.Has(dashboardtab.Name) { 198 t.Errorf("Duplicated name in dashboard %s: %v", dashboard.Name, dashboardtab.Name) 199 } else { 200 dashboardtabSet.Insert(dashboardtab.Name) 201 } 202 203 // All dashboardtab within a dashboard must not have duplicated testgroupnames 204 if dashboardtestgroupSet.Has(dashboardtab.TestGroupName) { 205 t.Errorf("Duplicated testgroupnames in dashboard %s: %v", dashboard.Name, dashboardtab.TestGroupName) 206 } else { 207 dashboardtestgroupSet.Insert(dashboardtab.TestGroupName) 208 } 209 210 // All testgroup in dashboard must be defined in testgroups 211 if testgroupMap[dashboardtab.TestGroupName] == 0 { 212 t.Errorf("Dashboard %v, tab %v: - Testgroup %v must be defined first", 213 dashboard.Name, dashboardtab.Name, dashboardtab.TestGroupName) 214 } else { 215 testgroupMap[dashboardtab.TestGroupName]++ 216 } 217 218 if dashboardtab.AlertOptions != nil && (dashboardtab.AlertOptions.AlertStaleResultsHours != 0 || dashboardtab.AlertOptions.NumFailuresToAlert != 0) { 219 for _, testgroup := range cfg.TestGroups { 220 // Disallow alert options in tab but not group. 221 // Disallow different alert options in tab vs. group. 222 if testgroup.Name == dashboardtab.TestGroupName { 223 if testgroup.AlertStaleResultsHours == 0 { 224 t.Errorf("Cannot define alert_stale_results_hours in DashboardTab %v and not TestGroup %v.", dashboardtab.Name, dashboardtab.TestGroupName) 225 } 226 if testgroup.NumFailuresToAlert == 0 { 227 t.Errorf("Cannot define num_failures_to_alert in DashboardTab %v and not TestGroup %v.", dashboardtab.Name, dashboardtab.TestGroupName) 228 } 229 if testgroup.AlertStaleResultsHours != dashboardtab.AlertOptions.AlertStaleResultsHours { 230 t.Errorf("alert_stale_results_hours for DashboardTab %v must match TestGroup %v.", dashboardtab.Name, dashboardtab.TestGroupName) 231 } 232 if testgroup.NumFailuresToAlert != dashboardtab.AlertOptions.NumFailuresToAlert { 233 t.Errorf("num_failures_to_alert for DashboardTab %v must match TestGroup %v.", dashboardtab.Name, dashboardtab.TestGroupName) 234 } 235 } 236 } 237 } 238 } 239 } 240 241 // No dup of dashboard groups, and no dup dashboard in a dashboard group 242 groups := make(map[string]bool) 243 tabs := make(map[string]string) 244 245 for idx, dashboardGroup := range cfg.DashboardGroups { 246 // All dashboard must have a name 247 if dashboardGroup.Name == "" { 248 t.Errorf("DashboardGroup %v: - DashboardGroup must have a name", idx) 249 } 250 251 found := false 252 for _, kind := range prefixes { 253 for _, prefix := range kind { 254 if strings.HasPrefix(dashboardGroup.Name, prefix+"-") || prefix == dashboardGroup.Name { 255 found = true 256 break 257 } 258 } 259 if found { 260 break 261 } 262 } 263 if !found { 264 t.Errorf("Dashboard group %v: must prefix with one of: %v", dashboardGroup.Name, prefixes) 265 } 266 267 // All dashboardgroup must not have duplicated names 268 if _, ok := groups[dashboardGroup.Name]; ok { 269 t.Errorf("Duplicated dashboard: %v", dashboardGroup.Name) 270 } else { 271 groups[dashboardGroup.Name] = true 272 } 273 274 if _, ok := dashboardmap[dashboardGroup.Name]; ok { 275 t.Errorf("%v is both a dashboard and dashboard group name.", dashboardGroup.Name) 276 } 277 278 for _, dashboard := range dashboardGroup.DashboardNames { 279 // All dashboard must not have duplicated names 280 if exist, ok := tabs[dashboard]; ok { 281 t.Errorf("Duplicated dashboard %v in dashboard group %v and %v", dashboard, exist, dashboardGroup.Name) 282 } else { 283 tabs[dashboard] = dashboardGroup.Name 284 } 285 286 if _, ok := dashboardmap[dashboard]; !ok { 287 t.Errorf("Dashboard %v needs to be defined before adding to a dashboard group!", dashboard) 288 } 289 290 if !strings.HasPrefix(dashboard, dashboardGroup.Name+"-") { 291 t.Errorf("Dashboard %v in group %v must have the group name as a prefix", dashboard, dashboardGroup.Name) 292 } 293 } 294 } 295 296 // All Testgroup should be mapped to one or more tabs 297 for testgroupname, occurrence := range testgroupMap { 298 if occurrence == 1 { 299 t.Errorf("Testgroup %v - defined but not used in any dashboards", testgroupname) 300 } 301 } 302 } 303 304 func TestJobsTestgridEntryMatch(t *testing.T) { 305 prowPath := "../../../prow/config.yaml" 306 jobPath := "../../../config/jobs" 307 308 jobs := make(map[string]bool) 309 310 prowConfig, err := prow_config.Load(prowPath, jobPath) 311 if err != nil { 312 t.Fatalf("Could not load prow configs: %v\n", err) 313 } 314 315 // Also check k/k presubmit, prow postsubmit and periodic jobs 316 for _, job := range prowConfig.AllPresubmits([]string{ 317 "bazelbuild/rules_k8s", 318 "google/cadvisor", 319 "helm/charts", 320 "GoogleCloudPlatform/k8s-cluster-bundle", 321 "kubeflow/arena", 322 "kubeflow/caffe2-operator", 323 "kubeflow/chainer-operator", 324 "kubeflow/examples", 325 "kubeflow/experimental-beagle", 326 "kubeflow/experimental-kvc", 327 "kubeflow/experimental-seldon", 328 "kubeflow/katib", 329 "kubeflow/kubebench", 330 "kubeflow/kubeflow", 331 "kubeflow/mpi-operator", 332 "kubeflow/mxnet-operator", 333 "kubeflow/pytorch-operator", 334 "kubeflow/reporting", 335 "kubeflow/testing", 336 "kubeflow/tf-operator", 337 "kubeflow/website", 338 "kubernetes/cloud-provider-aws", 339 "kubernetes/cloud-provider-vsphere", 340 "kubernetes/cluster-registry", 341 "kubernetes/federation", 342 "kubernetes/kops", 343 "kubernetes/kubernetes", 344 "kubernetes/org", 345 "kubernetes/publishing-bot", 346 "kubernetes/test-infra", 347 "kubernetes-sigs/aws-alb-ingress-controller", 348 "kubernetes-sigs/aws-ebs-csi-driver", 349 "kubernetes-sigs/cluster-api", 350 "kubernetes-sigs/cluster-api-provider-aws", 351 "kubernetes-sigs/cluster-api-provider-digitalocean", 352 "kubernetes-sigs/cluster-api-provider-gcp", 353 "kubernetes-sigs/cluster-api-provider-vsphere", 354 "kubernetes-sigs/cluster-api-provider-openstack", 355 "kubernetes-sigs/poseidon", 356 "kubernetes-sigs/structured-merge-diff", 357 "tensorflow/minigo", 358 }) { 359 jobs[job.Name] = false 360 } 361 362 for _, job := range prowConfig.AllPostsubmits([]string{}) { 363 jobs[job.Name] = false 364 } 365 366 for _, job := range prowConfig.AllPeriodics() { 367 jobs[job.Name] = false 368 } 369 370 // For now anything outsite k8s-jenkins/(pr-)logs are considered to be fine 371 testgroups := make(map[string]bool) 372 for _, testgroup := range cfg.TestGroups { 373 if strings.Contains(testgroup.GcsPrefix, "kubernetes-jenkins/logs/") { 374 // The convention is that the job name is the final part of the GcsPrefix 375 job := filepath.Base(testgroup.GcsPrefix) 376 testgroups[job] = false 377 } 378 379 if strings.Contains(testgroup.GcsPrefix, "kubernetes-jenkins/pr-logs/directory/") { 380 job := strings.TrimPrefix(testgroup.GcsPrefix, "kubernetes-jenkins/pr-logs/directory/") 381 testgroups[job] = false 382 } 383 } 384 385 // Cross check 386 // -- Each job need to have a match testgrid group 387 for job := range jobs { 388 if _, ok := testgroups[job]; ok { 389 testgroups[job] = true 390 jobs[job] = true 391 } 392 } 393 394 // Conclusion 395 badjobs := []string{} 396 for job, valid := range jobs { 397 if !valid { 398 badjobs = append(badjobs, job) 399 fmt.Printf("Job %v does not have a matching testgrid testgroup\n", job) 400 } 401 } 402 403 badconfigs := []string{} 404 for testgroup, valid := range testgroups { 405 if !valid { 406 badconfigs = append(badconfigs, testgroup) 407 fmt.Printf("Testgrid group %v does not have a matching jenkins or prow job\n", testgroup) 408 } 409 } 410 411 if len(badconfigs) > 0 { 412 fmt.Printf("Total bad config(s) - %v\n", len(badconfigs)) 413 } 414 415 if len(badjobs) > 0 { 416 fmt.Printf("Total bad job(s) - %v\n", len(badjobs)) 417 } 418 419 if len(badconfigs) > 0 || len(badjobs) > 0 { 420 t.Fatal("Failed with invalid config or job entries") 421 } 422 }