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)