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  }