github.com/yrj2011/jx-test-infra@v0.0.0-20190529031832-7a2065ee98eb/experiment/generate_tests.py (about) 1 #!/usr/bin/env python 2 3 # Copyright 2017 The Kubernetes Authors. 4 # 5 # Licensed under the Apache License, Version 2.0 (the "License"); 6 # you may not use this file except in compliance with the License. 7 # You may obtain a copy of the License at 8 # 9 # http://www.apache.org/licenses/LICENSE-2.0 10 # 11 # Unless required by applicable law or agreed to in writing, software 12 # distributed under the License is distributed on an "AS IS" BASIS, 13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 # See the License for the specific language governing permissions and 15 # limitations under the License. 16 17 """Create e2e test definitions. 18 19 Usage example: 20 21 In $GOPATH/src/k8s.io/test-infra, 22 23 $ bazel run //experiment:generate_tests -- \ 24 --yaml-config-path=experiment/test_config.yaml \ 25 """ 26 27 import argparse 28 import hashlib 29 import os 30 import ruamel.yaml as yaml 31 32 33 # TODO(yguo0905): Generate Prow and testgrid configurations. 34 35 PROW_CONFIG_TEMPLATE = """ 36 tags: 37 - generated # AUTO-GENERATED by experiment/generate_tests.py - DO NOT EDIT! 38 interval: 39 agent: kubernetes 40 labels: 41 preset-service-account: "true" 42 preset-k8s-ssh: "true" 43 name: 44 spec: 45 containers: 46 - args: 47 env: 48 image: gcr.io/k8s-testimages/kubekins-e2e:v20180725-795cceb4c-master 49 """ 50 51 COMMENT = 'AUTO-GENERATED by experiment/generate_tests.py - DO NOT EDIT.' 52 53 54 def get_sha1_hash(data): 55 """Returns the SHA1 hash of the specified data.""" 56 sha1_hash = hashlib.sha1() 57 sha1_hash.update(data) 58 return sha1_hash.hexdigest() 59 60 61 def substitute(job_name, lines): 62 """Replace '${job_name_hash}' in lines with the SHA1 hash of job_name.""" 63 return [line.replace('${job_name_hash}', get_sha1_hash(job_name)[:10]) \ 64 for line in lines] 65 66 def get_args(job_name, field): 67 """Returns a list of args for the given field.""" 68 if not field: 69 return [] 70 return substitute(job_name, field.get('args', [])) 71 72 73 def write_prow_configs_file(output_file, job_defs): 74 """Writes the Prow configurations into output_file.""" 75 with open(output_file, 'w') as fp: 76 yaml.dump( 77 job_defs, fp, Dumper=yaml.RoundTripDumper, width=float("inf")) 78 fp.write('\n') 79 80 81 def apply_job_overrides(envs_or_args, job_envs_or_args): 82 '''Applies the envs or args overrides defined in the job level''' 83 for job_env_or_arg in job_envs_or_args: 84 name = job_env_or_arg.split('=', 1)[0] 85 env_or_arg = next( 86 (x for x in envs_or_args if (x.strip().startswith('%s=' % name) or 87 x.strip() == name)), None) 88 if env_or_arg: 89 envs_or_args.remove(env_or_arg) 90 envs_or_args.append(job_env_or_arg) 91 92 93 class E2ENodeTest(object): 94 95 def __init__(self, job_name, job, config): 96 self.job_name = job_name 97 self.job = job 98 self.common = config['nodeCommon'] 99 self.images = config['nodeImages'] 100 self.k8s_versions = config['nodeK8sVersions'] 101 self.test_suites = config['nodeTestSuites'] 102 103 def __get_job_def(self, args): 104 """Returns the job definition from the given args.""" 105 return { 106 'scenario': 'kubernetes_e2e', 107 'args': args, 108 'sigOwners': self.job.get('sigOwners') or ['UNNOWN'], 109 # Indicates that this job definition is auto-generated. 110 'tags': ['generated'], 111 '_comment': COMMENT, 112 } 113 114 def __get_prow_config(self, test_suite, k8s_version): 115 """Returns the Prow config for the job from the given fields.""" 116 prow_config = yaml.round_trip_load(PROW_CONFIG_TEMPLATE) 117 prow_config['name'] = self.job_name 118 prow_config['interval'] = self.job['interval'] 119 # Assumes that the value in --timeout is of minutes. 120 timeout = int(next( 121 x[10:-1] for x in test_suite['args'] if ( 122 x.startswith('--timeout=')))) 123 container = prow_config['spec']['containers'][0] 124 if not container['args']: 125 container['args'] = [] 126 if not container['env']: 127 container['env'] = [] 128 # Prow timeout = job timeout + 20min 129 container['args'].append('--timeout=%d' % (timeout + 20)) 130 container['args'].extend(k8s_version.get('args', [])) 131 container['args'].append('--root=/go/src') 132 container['env'].extend([{'name':'GOPATH', 'value': '/go'}]) 133 # Specify the appropriate kubekins-e2e image. This allows us to use a 134 # specific image (containing a particular Go version) to build and 135 # trigger the node e2e test to avoid issues like 136 # https://github.com/kubernetes/kubernetes/issues/43534. 137 if k8s_version.get('prowImage', None): 138 container['image'] = k8s_version['prowImage'] 139 return prow_config 140 141 def generate(self): 142 '''Returns the job and the Prow configurations for this test.''' 143 fields = self.job_name.split('-') 144 if len(fields) != 6: 145 raise ValueError('Expected 6 fields in job name', self.job_name) 146 147 image = self.images[fields[3]] 148 k8s_version = self.k8s_versions[fields[4][3:]] 149 test_suite = self.test_suites[fields[5]] 150 151 # envs are disallowed in node e2e tests. 152 if 'envs' in self.common or 'envs' in image or 'envs' in test_suite: 153 raise ValueError( 154 'envs are disallowed in node e2e test', self.job_name) 155 # Generates args. 156 args = [] 157 args.extend(get_args(self.job_name, self.common)) 158 args.extend(get_args(self.job_name, image)) 159 args.extend(get_args(self.job_name, test_suite)) 160 # Generates job config. 161 job_config = self.__get_job_def(args) 162 # Generates prow config. 163 prow_config = self.__get_prow_config(test_suite, k8s_version) 164 165 # Combine --node-args 166 node_args = [] 167 job_args = [] 168 for arg in job_config['args']: 169 if '--node-args=' in arg: 170 node_args.append(arg.split('=', 1)[1]) 171 else: 172 job_args.append(arg) 173 174 if node_args: 175 flag = '--node-args=' 176 for node_arg in node_args: 177 flag += '%s ' % node_arg 178 job_args.append(flag.strip()) 179 180 job_config['args'] = job_args 181 182 return job_config, prow_config 183 184 185 class E2ETest(object): 186 187 def __init__(self, output_dir, job_name, job, config): 188 self.env_filename = os.path.join(output_dir, '%s.env' % job_name), 189 self.job_name = job_name 190 self.job = job 191 self.common = config['common'] 192 self.cloud_providers = config['cloudProviders'] 193 self.images = config['images'] 194 self.k8s_versions = config['k8sVersions'] 195 self.test_suites = config['testSuites'] 196 197 def __get_job_def(self, args): 198 """Returns the job definition from the given args.""" 199 return { 200 'scenario': 'kubernetes_e2e', 201 'args': args, 202 'sigOwners': self.job.get('sigOwners') or ['UNNOWN'], 203 # Indicates that this job definition is auto-generated. 204 'tags': ['generated'], 205 '_comment': COMMENT, 206 } 207 208 def __get_prow_config(self, test_suite): 209 """Returns the Prow config for the e2e job from the given fields.""" 210 prow_config = yaml.round_trip_load(PROW_CONFIG_TEMPLATE) 211 prow_config['name'] = self.job_name 212 prow_config['interval'] = self.job['interval'] 213 # Assumes that the value in --timeout is of minutes. 214 timeout = int(next( 215 x[10:-1] for x in test_suite['args'] if ( 216 x.startswith('--timeout=')))) 217 container = prow_config['spec']['containers'][0] 218 if not container['args']: 219 container['args'] = [] 220 container['args'].append('--bare') 221 # Prow timeout = job timeout + 20min 222 container['args'].append('--timeout=%d' % (timeout + 20)) 223 return prow_config 224 225 def generate(self): 226 '''Returns the job and the Prow configurations for this test.''' 227 fields = self.job_name.split('-') 228 if len(fields) != 7: 229 raise ValueError('Expected 7 fields in job name', self.job_name) 230 231 cloud_provider = self.cloud_providers[fields[3]] 232 image = self.images[fields[4]] 233 k8s_version = self.k8s_versions[fields[5][3:]] 234 test_suite = self.test_suites[fields[6]] 235 236 # Generates args. 237 args = [] 238 args.extend(get_args(self.job_name, self.common)) 239 args.extend(get_args(self.job_name, cloud_provider)) 240 args.extend(get_args(self.job_name, image)) 241 args.extend(get_args(self.job_name, k8s_version)) 242 args.extend(get_args(self.job_name, test_suite)) 243 # Generates job config. 244 job_config = self.__get_job_def(args) 245 # Generates Prow config. 246 prow_config = self.__get_prow_config(test_suite) 247 248 return job_config, prow_config 249 250 251 def for_each_job(output_dir, job_name, job, yaml_config): 252 """Returns the job config and the Prow config for one test job.""" 253 fields = job_name.split('-') 254 if len(fields) < 3: 255 raise ValueError('Expected at least 3 fields in job name', job_name) 256 job_type = fields[2] 257 258 # Generates configurations. 259 if job_type == 'e2e': 260 generator = E2ETest(output_dir, job_name, job, yaml_config) 261 elif job_type == 'e2enode': 262 generator = E2ENodeTest(job_name, job, yaml_config) 263 else: 264 raise ValueError('Unexpected job type ', job_type) 265 job_config, prow_config = generator.generate() 266 267 # Applies job-level overrides. 268 apply_job_overrides(job_config['args'], get_args(job_name, job)) 269 270 # merge job_config into prow_config 271 args = prow_config['spec']['containers'][0]['args'] 272 args.append('--scenario=' + job_config['scenario']) 273 args.append('--') 274 args.extend(job_config['args']) 275 276 return prow_config 277 278 279 def main(yaml_config_path, output_dir): 280 """Creates test job definitions. 281 282 Converts the test configurations in yaml_config_path to the job definitions 283 in output_dir/generated.yaml. 284 """ 285 # TODO(yguo0905): Validate the configurations from yaml_config_path. 286 287 with open(yaml_config_path) as fp: 288 yaml_config = yaml.safe_load(fp) 289 290 output_config = {} 291 output_config['periodics'] = [] 292 for job_name, _ in yaml_config['jobs'].items(): 293 # Get the envs and args for each job defined under "jobs". 294 prow = for_each_job( 295 output_dir, job_name, yaml_config['jobs'][job_name], yaml_config) 296 output_config['periodics'].append(prow) 297 298 # Write the job definitions to --output-dir/generated.yaml 299 write_prow_configs_file(output_dir + 'generated.yaml', output_config) 300 301 302 if __name__ == '__main__': 303 PARSER = argparse.ArgumentParser( 304 description='Create test definitions from the given yaml config') 305 PARSER.add_argument('--yaml-config-path', help='Path to config.yaml') 306 PARSER.add_argument( 307 '--output-dir', 308 help='Prowjob config output dir', 309 default='config/jobs/kubernetes/generated/') 310 ARGS = PARSER.parse_args() 311 312 main( 313 ARGS.yaml_config_path, 314 ARGS.output_dir)