vitess.io/vitess@v0.16.2/test/ci_workflow_gen.go (about)

     1  /*
     2  Copyright 2021 The Vitess 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  	"bytes"
    21  	"fmt"
    22  	"log"
    23  	"os"
    24  	"path"
    25  	"strings"
    26  	"text/template"
    27  )
    28  
    29  type mysqlVersion string
    30  
    31  const (
    32  	mysql57 mysqlVersion = "mysql57"
    33  	mysql80 mysqlVersion = "mysql80"
    34  
    35  	defaultMySQLVersion = mysql80
    36  )
    37  
    38  type mysqlVersions []mysqlVersion
    39  
    40  var (
    41  	defaultMySQLVersions = []mysqlVersion{defaultMySQLVersion}
    42  	allMySQLVersions     = []mysqlVersion{mysql57, mysql80}
    43  )
    44  
    45  var (
    46  	unitTestDatabases = []mysqlVersion{mysql57, mysql80}
    47  )
    48  
    49  const (
    50  	workflowConfigDir = "../.github/workflows"
    51  
    52  	unitTestTemplate = "templates/unit_test.tpl"
    53  
    54  	// An empty string will cause the default non platform specific template
    55  	// to be used.
    56  	clusterTestTemplate = "templates/cluster_endtoend_test%s.tpl"
    57  
    58  	unitTestSelfHostedTemplate    = "templates/unit_test_self_hosted.tpl"
    59  	unitTestSelfHostedDatabases   = ""
    60  	dockerFileTemplate            = "templates/dockerfile.tpl"
    61  	clusterTestSelfHostedTemplate = "templates/cluster_endtoend_test_self_hosted.tpl"
    62  	clusterTestDockerTemplate     = "templates/cluster_endtoend_test_docker.tpl"
    63  )
    64  
    65  var (
    66  	// Clusters 10, 25 are executed on docker, using the docker_test_cluster 10, 25 workflows.
    67  	// Hence, they are not listed in the list below.
    68  	clusterList = []string{
    69  		"vtctlbackup_sharded_clustertest_heavy",
    70  		"12",
    71  		"13",
    72  		"ers_prs_newfeatures_heavy",
    73  		"15",
    74  		"vtgate_general_heavy",
    75  		"vtbackup",
    76  		"18",
    77  		"xb_backup",
    78  		"backup_pitr",
    79  		"21",
    80  		"22",
    81  		"mysql_server_vault",
    82  		"vstream_failover",
    83  		"vstream_stoponreshard_true",
    84  		"vstream_stoponreshard_false",
    85  		"vstream_with_keyspaces_to_watch",
    86  		"onlineddl_ghost",
    87  		"onlineddl_vrepl",
    88  		"onlineddl_vrepl_stress",
    89  		"onlineddl_vrepl_stress_suite",
    90  		"onlineddl_vrepl_suite",
    91  		"vreplication_migrate_vdiff2_convert_tz",
    92  		"onlineddl_revert",
    93  		"onlineddl_scheduler",
    94  		"tabletmanager_throttler",
    95  		"tabletmanager_throttler_topo",
    96  		"tabletmanager_throttler_custom_config",
    97  		"tabletmanager_tablegc",
    98  		"tabletmanager_consul",
    99  		"vtgate_concurrentdml",
   100  		"vtgate_godriver",
   101  		"vtgate_gen4",
   102  		"vtgate_readafterwrite",
   103  		"vtgate_reservedconn",
   104  		"vtgate_schema",
   105  		"vtgate_tablet_healthcheck_cache",
   106  		"vtgate_topo",
   107  		"vtgate_topo_consul",
   108  		"vtgate_topo_etcd",
   109  		"vtgate_transaction",
   110  		"vtgate_unsharded",
   111  		"vtgate_vindex_heavy",
   112  		"vtgate_vschema",
   113  		"vtgate_queries",
   114  		"vtgate_schema_tracker",
   115  		"vtorc",
   116  		"xb_recovery",
   117  		"mysql80",
   118  		"vreplication_across_db_versions",
   119  		"vreplication_multicell",
   120  		"vreplication_cellalias",
   121  		"vreplication_basic",
   122  		"vreplication_v2",
   123  		"schemadiff_vrepl",
   124  		"topo_connection_cache",
   125  		"vtgate_partial_keyspace",
   126  		"vttablet_prscomplex",
   127  	}
   128  
   129  	clusterSelfHostedList       = []string{}
   130  	clusterDockerList           = []string{}
   131  	clustersRequiringXtraBackup = []string{
   132  		"xb_backup",
   133  		"xb_recovery",
   134  	}
   135  	clustersRequiringMakeTools = []string{
   136  		"18",
   137  		"mysql_server_vault",
   138  		"vtgate_topo_consul",
   139  		"tabletmanager_consul",
   140  	}
   141  )
   142  
   143  type unitTest struct {
   144  	Name, Platform, FileName string
   145  }
   146  
   147  type clusterTest struct {
   148  	Name, Shard, Platform        string
   149  	FileName                     string
   150  	MakeTools, InstallXtraBackup bool
   151  	Docker                       bool
   152  	LimitResourceUsage           bool
   153  	PartialKeyspace              bool
   154  }
   155  
   156  type selfHostedTest struct {
   157  	Name, Platform, Dockerfile, Shard, ImageName, directoryName string
   158  	FileName                                                    string
   159  	MakeTools, InstallXtraBackup, Docker                        bool
   160  }
   161  
   162  // clusterMySQLVersions return list of mysql versions (one or more) that this cluster needs to test against
   163  func clusterMySQLVersions(clusterName string) mysqlVersions {
   164  	switch {
   165  	case strings.HasPrefix(clusterName, "onlineddl_"):
   166  		return allMySQLVersions
   167  	case clusterName == "schemadiff_vrepl":
   168  		return allMySQLVersions
   169  	case clusterName == "backup_pitr":
   170  		return allMySQLVersions
   171  	case clusterName == "tabletmanager_tablegc":
   172  		return allMySQLVersions
   173  	case clusterName == "vtorc":
   174  		return allMySQLVersions
   175  	case clusterName == "xb_backup":
   176  		return allMySQLVersions
   177  	case clusterName == "xb_recovery":
   178  		return allMySQLVersions
   179  	default:
   180  		return defaultMySQLVersions
   181  	}
   182  }
   183  
   184  func mergeBlankLines(buf *bytes.Buffer) string {
   185  	var out []string
   186  	in := strings.Split(buf.String(), "\n")
   187  	lastWasBlank := false
   188  	for _, line := range in {
   189  		if strings.TrimSpace(line) == "" {
   190  			if lastWasBlank {
   191  				continue
   192  			}
   193  			lastWasBlank = true
   194  		} else {
   195  			lastWasBlank = false
   196  		}
   197  
   198  		out = append(out, line)
   199  	}
   200  	return strings.Join(out, "\n")
   201  }
   202  
   203  func main() {
   204  	generateUnitTestWorkflows()
   205  	generateClusterWorkflows(clusterList, clusterTestTemplate)
   206  	generateClusterWorkflows(clusterDockerList, clusterTestDockerTemplate)
   207  
   208  	// tests that will use self-hosted runners
   209  	err := generateSelfHostedUnitTestWorkflows()
   210  	if err != nil {
   211  		log.Fatal(err)
   212  	}
   213  	err = generateSelfHostedClusterWorkflows()
   214  	if err != nil {
   215  		log.Fatal(err)
   216  	}
   217  }
   218  
   219  func canonnizeList(list []string) []string {
   220  	var output []string
   221  	for _, item := range list {
   222  		if item := strings.TrimSpace(item); item != "" {
   223  			output = append(output, item)
   224  		}
   225  	}
   226  	return output
   227  }
   228  
   229  func parseList(csvList string) []string {
   230  	var list []string
   231  	for _, item := range strings.Split(csvList, ",") {
   232  		if item != "" {
   233  			list = append(list, strings.TrimSpace(item))
   234  		}
   235  	}
   236  	return list
   237  }
   238  
   239  func generateSelfHostedUnitTestWorkflows() error {
   240  	platforms := parseList(unitTestSelfHostedDatabases)
   241  	for _, platform := range platforms {
   242  		directoryName := fmt.Sprintf("unit_test_%s", platform)
   243  		test := &selfHostedTest{
   244  			Name:              fmt.Sprintf("Unit Test (%s)", platform),
   245  			ImageName:         fmt.Sprintf("unit_test_%s", platform),
   246  			Platform:          platform,
   247  			directoryName:     directoryName,
   248  			Dockerfile:        fmt.Sprintf("./.github/docker/%s/Dockerfile", directoryName),
   249  			MakeTools:         true,
   250  			InstallXtraBackup: false,
   251  		}
   252  		err := setupTestDockerFile(test)
   253  		if err != nil {
   254  			return err
   255  		}
   256  		test.FileName = fmt.Sprintf("unit_test_%s.yml", platform)
   257  		filePath := fmt.Sprintf("%s/%s", workflowConfigDir, test.FileName)
   258  		err = writeFileFromTemplate(unitTestSelfHostedTemplate, filePath, test)
   259  		if err != nil {
   260  			log.Print(err)
   261  		}
   262  	}
   263  	return nil
   264  }
   265  
   266  func generateSelfHostedClusterWorkflows() error {
   267  	clusters := canonnizeList(clusterSelfHostedList)
   268  	for _, cluster := range clusters {
   269  		for _, mysqlVersion := range clusterMySQLVersions(cluster) {
   270  			// check mysqlversion
   271  			mysqlVersionIndicator := ""
   272  			if mysqlVersion != defaultMySQLVersion && len(clusterMySQLVersions(cluster)) > 1 {
   273  				mysqlVersionIndicator = "_" + string(mysqlVersion)
   274  			}
   275  
   276  			directoryName := fmt.Sprintf("cluster_test_%s%s", cluster, mysqlVersionIndicator)
   277  			test := &selfHostedTest{
   278  				Name:              fmt.Sprintf("Cluster (%s)(%s)", cluster, mysqlVersion),
   279  				ImageName:         fmt.Sprintf("cluster_test_%s%s", cluster, mysqlVersionIndicator),
   280  				Platform:          "mysql80",
   281  				directoryName:     directoryName,
   282  				Dockerfile:        fmt.Sprintf("./.github/docker/%s/Dockerfile", directoryName),
   283  				Shard:             cluster,
   284  				MakeTools:         false,
   285  				InstallXtraBackup: false,
   286  			}
   287  			makeToolClusters := canonnizeList(clustersRequiringMakeTools)
   288  			for _, makeToolCluster := range makeToolClusters {
   289  				if makeToolCluster == cluster {
   290  					test.MakeTools = true
   291  					break
   292  				}
   293  			}
   294  			xtraBackupClusters := canonnizeList(clustersRequiringXtraBackup)
   295  			for _, xtraBackupCluster := range xtraBackupClusters {
   296  				if xtraBackupCluster == cluster {
   297  					test.InstallXtraBackup = true
   298  					break
   299  				}
   300  			}
   301  			if mysqlVersion == mysql57 {
   302  				test.Platform = string(mysql57)
   303  			}
   304  
   305  			err := setupTestDockerFile(test)
   306  			if err != nil {
   307  				return err
   308  			}
   309  
   310  			test.FileName = fmt.Sprintf("cluster_endtoend_%s%s.yml", cluster, mysqlVersionIndicator)
   311  			filePath := fmt.Sprintf("%s/%s", workflowConfigDir, test.FileName)
   312  			err = writeFileFromTemplate(clusterTestSelfHostedTemplate, filePath, test)
   313  			if err != nil {
   314  				log.Print(err)
   315  			}
   316  		}
   317  	}
   318  	return nil
   319  }
   320  
   321  func generateClusterWorkflows(list []string, tpl string) {
   322  	clusters := canonnizeList(list)
   323  	for _, cluster := range clusters {
   324  		for _, mysqlVersion := range clusterMySQLVersions(cluster) {
   325  			test := &clusterTest{
   326  				Name:  fmt.Sprintf("Cluster (%s)", cluster),
   327  				Shard: cluster,
   328  			}
   329  			makeToolClusters := canonnizeList(clustersRequiringMakeTools)
   330  			for _, makeToolCluster := range makeToolClusters {
   331  				if makeToolCluster == cluster {
   332  					test.MakeTools = true
   333  					break
   334  				}
   335  			}
   336  			xtraBackupClusters := canonnizeList(clustersRequiringXtraBackup)
   337  			for _, xtraBackupCluster := range xtraBackupClusters {
   338  				if xtraBackupCluster == cluster {
   339  					test.InstallXtraBackup = true
   340  					break
   341  				}
   342  			}
   343  			if mysqlVersion == mysql57 {
   344  				test.Platform = string(mysql57)
   345  			}
   346  			if strings.HasPrefix(cluster, "vreplication") || strings.HasSuffix(cluster, "heavy") {
   347  				test.LimitResourceUsage = true
   348  			}
   349  			mysqlVersionIndicator := ""
   350  			if mysqlVersion != defaultMySQLVersion && len(clusterMySQLVersions(cluster)) > 1 {
   351  				mysqlVersionIndicator = "_" + string(mysqlVersion)
   352  				test.Name = test.Name + " " + string(mysqlVersion)
   353  			}
   354  			if strings.Contains(test.Shard, "partial_keyspace") {
   355  				test.PartialKeyspace = true
   356  			}
   357  
   358  			workflowPath := fmt.Sprintf("%s/cluster_endtoend_%s%s.yml", workflowConfigDir, cluster, mysqlVersionIndicator)
   359  			templateFileName := tpl
   360  			if test.Platform != "" {
   361  				templateFileName = fmt.Sprintf(tpl, "_"+test.Platform)
   362  			} else if strings.Contains(templateFileName, "%s") {
   363  				templateFileName = fmt.Sprintf(tpl, "")
   364  			}
   365  			test.FileName = fmt.Sprintf("cluster_endtoend_%s%s.yml", cluster, mysqlVersionIndicator)
   366  			err := writeFileFromTemplate(templateFileName, workflowPath, test)
   367  			if err != nil {
   368  				log.Print(err)
   369  			}
   370  		}
   371  	}
   372  }
   373  
   374  func generateUnitTestWorkflows() {
   375  	for _, platform := range unitTestDatabases {
   376  		test := &unitTest{
   377  			Name:     fmt.Sprintf("Unit Test (%s)", platform),
   378  			Platform: string(platform),
   379  		}
   380  		test.FileName = fmt.Sprintf("unit_test_%s.yml", platform)
   381  		path := fmt.Sprintf("%s/%s", workflowConfigDir, test.FileName)
   382  		err := writeFileFromTemplate(unitTestTemplate, path, test)
   383  		if err != nil {
   384  			log.Print(err)
   385  		}
   386  	}
   387  }
   388  
   389  func setupTestDockerFile(test *selfHostedTest) error {
   390  	// remove the directory
   391  	relDirectoryName := fmt.Sprintf("../.github/docker/%s", test.directoryName)
   392  	err := os.RemoveAll(relDirectoryName)
   393  	if err != nil {
   394  		return err
   395  	}
   396  	// create the directory
   397  	err = os.MkdirAll(relDirectoryName, 0755)
   398  	if err != nil {
   399  		return err
   400  	}
   401  
   402  	// generate the docker file
   403  	dockerFilePath := path.Join(relDirectoryName, "Dockerfile")
   404  	err = writeFileFromTemplate(dockerFileTemplate, dockerFilePath, test)
   405  	if err != nil {
   406  		return err
   407  	}
   408  
   409  	return nil
   410  }
   411  
   412  func writeFileFromTemplate(templateFile, filePath string, test any) error {
   413  	tpl := template.New(path.Base(templateFile))
   414  	tpl.Funcs(template.FuncMap{
   415  		"contains": strings.Contains,
   416  	})
   417  	tpl, err := tpl.ParseFiles(templateFile)
   418  	if err != nil {
   419  		return fmt.Errorf("Error: %s\n", err)
   420  	}
   421  
   422  	buf := &bytes.Buffer{}
   423  	err = tpl.Execute(buf, test)
   424  	if err != nil {
   425  		return fmt.Errorf("Error: %s\n", err)
   426  	}
   427  
   428  	f, err := os.Create(filePath)
   429  	if err != nil {
   430  		return fmt.Errorf("Error creating file: %s\n", err)
   431  	}
   432  	if _, err := f.WriteString("# DO NOT MODIFY: THIS FILE IS GENERATED USING \"make generate_ci_workflows\"\n\n"); err != nil {
   433  		return err
   434  	}
   435  	if _, err := f.WriteString(mergeBlankLines(buf)); err != nil {
   436  		return err
   437  	}
   438  	fmt.Printf("Generated %s\n", filePath)
   439  	return nil
   440  }