github.com/yrj2011/jx-test-infra@v0.0.0-20190529031832-7a2065ee98eb/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 "github.com/ghodss/yaml" 29 prow_config "k8s.io/test-infra/prow/config" 30 config_pb "k8s.io/test-infra/testgrid/config" 31 ) 32 33 type SQConfig struct { 34 Data map[string]string `yaml:"data,omitempty"` 35 } 36 37 var ( 38 companies = []string{ 39 "canonical", 40 "cri-o", 41 "istio", 42 "google", 43 "kopeio", 44 "tectonic", 45 "redhat", 46 } 47 orgs = []string{ 48 "conformance", 49 "presubmits", 50 "sig", 51 "wg", 52 } 53 prefixes = [][]string{orgs, companies} 54 ) 55 56 // Shared testgrid config, loaded at TestMain. 57 var cfg *config_pb.Configuration 58 59 func TestMain(m *testing.M) { 60 //make sure we can parse config.yaml 61 yamlData, err := ioutil.ReadFile("../../config.yaml") 62 if err != nil { 63 fmt.Printf("IO Error : Cannot Open File config.yaml") 64 os.Exit(1) 65 } 66 67 c := Config{} 68 if err := c.Update(yamlData); err != nil { 69 fmt.Printf("Yaml2Proto - Conversion Error %v", err) 70 os.Exit(1) 71 } 72 73 cfg, err = c.Raw() 74 if err != nil { 75 fmt.Printf("Error validating config: %v", err) 76 os.Exit(1) 77 } 78 79 os.Exit(m.Run()) 80 } 81 82 func TestConfig(t *testing.T) { 83 // testgroup - occurrence map, validate testgroups 84 testgroupMap := make(map[string]int32) 85 86 for testgroupidx, testgroup := range cfg.TestGroups { 87 // All testgroup must have a name and a query 88 if testgroup.Name == "" || testgroup.GcsPrefix == "" { 89 t.Errorf("Testgroup #%v (Name: '%v', Query: '%v'): - Must have a name and query", 90 testgroupidx, testgroup.Name, testgroup.GcsPrefix) 91 } 92 93 // All testgroup must not have duplicated names 94 if testgroupMap[testgroup.Name] > 0 { 95 t.Errorf("Duplicated Testgroup: %v", testgroup.Name) 96 } else { 97 testgroupMap[testgroup.Name] = 1 98 } 99 100 if !testgroup.IsExternal { 101 t.Errorf("Testgroup %v: IsExternal should always be true!", testgroup.Name) 102 } 103 if !testgroup.UseKubernetesClient { 104 t.Errorf("Testgroup %v: UseKubernetesClient should always be true!", testgroup.Name) 105 } 106 107 if strings.HasPrefix(testgroup.GcsPrefix, "kubernetes-jenkins/logs/") { 108 // The expectation is that testgroup.Name is the name of a Prow job and the GCSPrefix 109 // follows the convention kubernetes-jenkins/logs/.../jobName 110 // The final part of the prefix should be the job name. 111 expected := filepath.Join(filepath.Dir(testgroup.GcsPrefix), testgroup.Name) 112 if expected != testgroup.GcsPrefix { 113 t.Errorf("Kubernetes Testgroup %v GcsPrefix; Got %v; Want %v", testgroup.Name, testgroup.GcsPrefix, expected) 114 } 115 } 116 117 if testgroup.TestNameConfig != nil { 118 if testgroup.TestNameConfig.NameFormat == "" { 119 t.Errorf("Testgroup %v: NameFormat must not be empty!", testgroup.Name) 120 } 121 122 if len(testgroup.TestNameConfig.NameElements) != strings.Count(testgroup.TestNameConfig.NameFormat, "%") { 123 t.Errorf("Testgroup %v: TestNameConfig must have number NameElement equal to format count in NameFormat!", testgroup.Name) 124 } 125 } 126 127 // All PR testgroup has num_columns_recent equals 20 128 if strings.HasPrefix(testgroup.GcsPrefix, "kubernetes-jenkins/pr-logs/directory/") { 129 if testgroup.NumColumnsRecent < 20 { 130 t.Errorf("Testgroup %v: num_columns_recent: must be greater than 20 for presubmit jobs!", testgroup.Name) 131 } 132 } 133 } 134 135 // dashboard name set 136 dashboardmap := make(map[string]bool) 137 138 for dashboardidx, dashboard := range cfg.Dashboards { 139 // All dashboard must have a name 140 if dashboard.Name == "" { 141 t.Errorf("Dashboard %v: - Must have a name", dashboardidx) 142 } 143 144 found := false 145 for _, kind := range prefixes { 146 for _, prefix := range kind { 147 if strings.HasPrefix(dashboard.Name, prefix+"-") || dashboard.Name == prefix { 148 found = true 149 break 150 } 151 } 152 if found { 153 break 154 } 155 } 156 if !found { 157 t.Errorf("Dashboard %v: must prefix with one of: %v", dashboard.Name, prefixes) 158 } 159 160 // All dashboard must not have duplicated names 161 if dashboardmap[dashboard.Name] { 162 t.Errorf("Duplicated dashboard: %v", dashboard.Name) 163 } else { 164 dashboardmap[dashboard.Name] = true 165 } 166 167 // All dashboard must have at least one tab 168 if len(dashboard.DashboardTab) == 0 { 169 t.Errorf("Dashboard %v: - Must have more than one dashboardtab", dashboard.Name) 170 } 171 172 // dashboardtab name set, to check duplicated tabs within each dashboard 173 dashboardtabmap := make(map[string]bool) 174 175 // All notifications in dashboard must have a summary 176 if len(dashboard.Notifications) != 0 { 177 for notificationindex, notification := range dashboard.Notifications { 178 if notification.Summary == "" { 179 t.Errorf("Notification %v in dashboard %v: - Must have a summary", notificationindex, dashboard.Name) 180 } 181 } 182 } 183 184 for tabindex, dashboardtab := range dashboard.DashboardTab { 185 186 // All dashboardtab must have a name and a testgroup 187 if dashboardtab.Name == "" || dashboardtab.TestGroupName == "" { 188 t.Errorf("Dashboard %v, tab %v: - Must have a name and a testgroup name", dashboard.Name, tabindex) 189 } 190 191 // All dashboardtab within a dashboard must not have duplicated names 192 if dashboardtabmap[dashboardtab.Name] { 193 t.Errorf("Duplicated dashboardtab: %v", dashboardtab.Name) 194 } else { 195 dashboardtabmap[dashboardtab.Name] = true 196 } 197 198 // All testgroup in dashboard must be defined in testgroups 199 if testgroupMap[dashboardtab.TestGroupName] == 0 { 200 t.Errorf("Dashboard %v, tab %v: - Testgroup %v must be defined first", 201 dashboard.Name, dashboardtab.Name, dashboardtab.TestGroupName) 202 } else { 203 testgroupMap[dashboardtab.TestGroupName]++ 204 } 205 206 if dashboardtab.AlertOptions != nil && (dashboardtab.AlertOptions.AlertStaleResultsHours != 0 || dashboardtab.AlertOptions.NumFailuresToAlert != 0) { 207 for _, testgroup := range cfg.TestGroups { 208 // Disallow alert options in tab but not group. 209 // Disallow different alert options in tab vs. group. 210 if testgroup.Name == dashboardtab.TestGroupName { 211 if testgroup.AlertStaleResultsHours == 0 { 212 t.Errorf("Cannot define alert_stale_results_hours in DashboardTab %v and not TestGroup %v.", dashboardtab.Name, dashboardtab.TestGroupName) 213 } 214 if testgroup.NumFailuresToAlert == 0 { 215 t.Errorf("Cannot define num_failures_to_alert in DashboardTab %v and not TestGroup %v.", dashboardtab.Name, dashboardtab.TestGroupName) 216 } 217 if testgroup.AlertStaleResultsHours != dashboardtab.AlertOptions.AlertStaleResultsHours { 218 t.Errorf("alert_stale_results_hours for DashboardTab %v must match TestGroup %v.", dashboardtab.Name, dashboardtab.TestGroupName) 219 } 220 if testgroup.NumFailuresToAlert != dashboardtab.AlertOptions.NumFailuresToAlert { 221 t.Errorf("num_failures_to_alert for DashboardTab %v must match TestGroup %v.", dashboardtab.Name, dashboardtab.TestGroupName) 222 } 223 } 224 } 225 } 226 } 227 } 228 229 // No dup of dashboard groups, and no dup dashboard in a dashboard group 230 groups := make(map[string]bool) 231 tabs := make(map[string]string) 232 233 for idx, dashboardGroup := range cfg.DashboardGroups { 234 // All dashboard must have a name 235 if dashboardGroup.Name == "" { 236 t.Errorf("DashboardGroup %v: - DashboardGroup must have a name", idx) 237 } 238 239 found := false 240 for _, kind := range prefixes { 241 for _, prefix := range kind { 242 if strings.HasPrefix(dashboardGroup.Name, prefix+"-") || prefix == dashboardGroup.Name { 243 found = true 244 break 245 } 246 } 247 if found { 248 break 249 } 250 } 251 if !found { 252 t.Errorf("Dashboard group %v: must prefix with one of: %v", dashboardGroup.Name, prefixes) 253 } 254 255 // All dashboardgroup must not have duplicated names 256 if _, ok := groups[dashboardGroup.Name]; ok { 257 t.Errorf("Duplicated dashboard: %v", dashboardGroup.Name) 258 } else { 259 groups[dashboardGroup.Name] = true 260 } 261 262 if _, ok := dashboardmap[dashboardGroup.Name]; ok { 263 t.Errorf("%v is both a dashboard and dashboard group name.", dashboardGroup.Name) 264 } 265 266 for _, dashboard := range dashboardGroup.DashboardNames { 267 // All dashboard must not have duplicated names 268 if exist, ok := tabs[dashboard]; ok { 269 t.Errorf("Duplicated dashboard %v in dashboard group %v and %v", dashboard, exist, dashboardGroup.Name) 270 } else { 271 tabs[dashboard] = dashboardGroup.Name 272 } 273 274 if _, ok := dashboardmap[dashboard]; !ok { 275 t.Errorf("Dashboard %v needs to be defined before adding to a dashboard group!", dashboard) 276 } 277 278 if !strings.HasPrefix(dashboard, dashboardGroup.Name+"-") { 279 t.Errorf("Dashboard %v in group %v must have the group name as a prefix", dashboard, dashboardGroup.Name) 280 } 281 } 282 } 283 284 // All Testgroup should be mapped to one or more tabs 285 for testgroupname, occurrence := range testgroupMap { 286 if occurrence == 1 { 287 t.Errorf("Testgroup %v - defined but not used in any dashboards", testgroupname) 288 } 289 } 290 291 // make sure items in sq-blocking dashboard matches sq configmap 292 sqJobPool := []string{} 293 for _, d := range cfg.Dashboards { 294 if d.Name != "sq-blocking" { 295 continue 296 } 297 298 for _, tab := range d.DashboardTab { 299 for _, t := range cfg.TestGroups { 300 if t.Name == tab.TestGroupName { 301 job := strings.TrimPrefix(t.GcsPrefix, "kubernetes-jenkins/logs/") 302 sqJobPool = append(sqJobPool, job) 303 break 304 } 305 } 306 } 307 } 308 309 sqConfigPath := "../../../mungegithub/submit-queue/deployment/kubernetes/configmap.yaml" 310 configData, err := ioutil.ReadFile(sqConfigPath) 311 if err != nil { 312 t.Errorf("Read Buffer Error for SQ Data : %v", err) 313 } 314 315 sqData := &SQConfig{} 316 err = yaml.Unmarshal([]byte(configData), &sqData) 317 if err != nil { 318 t.Errorf("Unmarshal Error for SQ Data : %v", err) 319 } 320 321 for _, testgridJob := range sqJobPool { 322 t.Errorf("Err : testgrid job %v not found in SQ config", testgridJob) 323 } 324 325 sqNonBlockingJobs := strings.Split(sqData.Data["nonblocking-jobs"], ",") 326 for _, sqJob := range sqNonBlockingJobs { 327 if sqJob == "" { // ignore empty list of jobs 328 continue 329 } 330 found := false 331 for _, testgroup := range cfg.TestGroups { 332 if testgroup.Name == sqJob { 333 found = true 334 break 335 } 336 } 337 338 if !found { 339 t.Errorf("Err : %v not found in testgrid config", sqJob) 340 } 341 } 342 } 343 344 func TestJobsTestgridEntryMatch(t *testing.T) { 345 prowPath := "../../../prow/config.yaml" 346 jobPath := "../../../config/jobs" 347 348 jobs := make(map[string]bool) 349 350 prowConfig, err := prow_config.Load(prowPath, jobPath) 351 if err != nil { 352 t.Fatalf("Could not load prow configs: %v\n", err) 353 } 354 355 // Also check k/k presubmit, prow postsubmit and periodic jobs 356 for _, job := range prowConfig.AllPresubmits([]string{ 357 "bazelbuild/rules_k8s", 358 "google/cadvisor", 359 "helm/charts", 360 "kubeflow/caffe2-operator", 361 "kubeflow/examples", 362 "kubeflow/experimental-beagle", 363 "kubeflow/experimental-kvc", 364 "kubeflow/experimental-seldon", 365 "kubeflow/katib", 366 "kubeflow/kubebench", 367 "kubeflow/kubeflow", 368 "kubeflow/mpi-operator", 369 "kubeflow/pytorch-operator", 370 "kubeflow/reporting", 371 "kubeflow/testing", 372 "kubeflow/tf-operator", 373 "kubeflow/website", 374 "kubernetes-sigs/cluster-api", 375 "kubernetes-sigs/cluster-api-provider-aws", 376 "kubernetes-sigs/cluster-api-provider-gcp", 377 "kubernetes-sigs/cluster-api-provider-openstack", 378 "kubernetes-sigs/poseidon", 379 "kubernetes/cluster-registry", 380 "kubernetes/cloud-provider-vsphere", 381 "kubernetes/federation", 382 "kubernetes/heapster", 383 "kubernetes/kops", 384 "kubernetes/kubernetes", 385 "kubernetes/test-infra", 386 "tensorflow/minigo", 387 }) { 388 jobs[job.Name] = false 389 } 390 391 for _, job := range prowConfig.AllPostsubmits([]string{}) { 392 jobs[job.Name] = false 393 } 394 395 for _, job := range prowConfig.AllPeriodics() { 396 jobs[job.Name] = false 397 } 398 399 // For now anything outsite k8s-jenkins/(pr-)logs are considered to be fine 400 testgroups := make(map[string]bool) 401 for _, testgroup := range cfg.TestGroups { 402 if strings.Contains(testgroup.GcsPrefix, "kubernetes-jenkins/logs/") { 403 // The convention is that the job name is the final part of the GcsPrefix 404 job := filepath.Base(testgroup.GcsPrefix) 405 testgroups[job] = false 406 } 407 408 if strings.Contains(testgroup.GcsPrefix, "kubernetes-jenkins/pr-logs/directory/") { 409 job := strings.TrimPrefix(testgroup.GcsPrefix, "kubernetes-jenkins/pr-logs/directory/") 410 testgroups[job] = false 411 } 412 } 413 414 // Cross check 415 // -- Each job need to have a match testgrid group 416 for job := range jobs { 417 if _, ok := testgroups[job]; ok { 418 testgroups[job] = true 419 jobs[job] = true 420 } 421 } 422 423 // Conclusion 424 badjobs := []string{} 425 for job, valid := range jobs { 426 if !valid { 427 badjobs = append(badjobs, job) 428 fmt.Printf("Job %v does not have a matching testgrid testgroup\n", job) 429 } 430 } 431 432 badconfigs := []string{} 433 for testgroup, valid := range testgroups { 434 if !valid { 435 badconfigs = append(badconfigs, testgroup) 436 fmt.Printf("Testgrid group %v does not have a matching jenkins or prow job\n", testgroup) 437 } 438 } 439 440 if len(badconfigs) > 0 { 441 fmt.Printf("Total bad config(s) - %v\n", len(badconfigs)) 442 } 443 444 if len(badjobs) > 0 { 445 fmt.Printf("Total bad job(s) - %v\n", len(badjobs)) 446 } 447 448 if len(badconfigs) > 0 || len(badjobs) > 0 { 449 t.Fatal("Failed with invalid config or job entries") 450 } 451 }