github.com/maxgio92/test-infra@v0.1.0/scenarios/kubernetes_e2e.py (about)

     1  #!/usr/bin/env python3
     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  # Need to figure out why this only fails on travis
    18  # pylint: disable=bad-continuation
    19  
    20  """Runs kubernetes e2e test with specified config"""
    21  
    22  import argparse
    23  import hashlib
    24  import os
    25  import random
    26  import re
    27  import shutil
    28  import subprocess
    29  import sys
    30  import urllib.request, urllib.error, urllib.parse
    31  import time
    32  
    33  ORIG_CWD = os.getcwd()  # Checkout changes cwd
    34  
    35  # The zones below are the zones available in the CNCF account (in theory, zones vary by account)
    36  # We aim for 3 zones per region to try to maintain even spreading.
    37  # We also remove a few zones where our preferred instance type is not available,
    38  # though really this needs a better fix (likely in kops)
    39  DEFAULT_AWS_ZONES = [
    40      'ap-northeast-1a',
    41      'ap-northeast-1c',
    42      'ap-northeast-1d',
    43      'ap-northeast-2a',
    44      #'ap-northeast-2b' - AZ does not exist, so we're breaking the 3 AZs per region target here
    45      'ap-northeast-2c',
    46      'ap-south-1a',
    47      'ap-south-1b',
    48      'ap-southeast-1a',
    49      'ap-southeast-1b',
    50      'ap-southeast-1c',
    51      'ap-southeast-2a',
    52      'ap-southeast-2b',
    53      'ap-southeast-2c',
    54      'ca-central-1a',
    55      'ca-central-1b',
    56      'eu-central-1a',
    57      'eu-central-1b',
    58      'eu-central-1c',
    59      'eu-west-1a',
    60      'eu-west-1b',
    61      'eu-west-1c',
    62      'eu-west-2a',
    63      'eu-west-2b',
    64      'eu-west-2c',
    65      #'eu-west-3a', documented to not support c4 family
    66      #'eu-west-3b', documented to not support c4 family
    67      #'eu-west-3c', documented to not support c4 family
    68      'sa-east-1a',
    69      #'sa-east-1b', AZ does not exist, so we're breaking the 3 AZs per region target here
    70      'sa-east-1c',
    71      #'us-east-1a', # temporarily removing due to lack of quota #10043
    72      #'us-east-1b', # temporarily removing due to lack of quota #10043
    73      #'us-east-1c', # temporarily removing due to lack of quota #10043
    74      #'us-east-1d', # limiting to 3 zones to not overallocate
    75      #'us-east-1e', # limiting to 3 zones to not overallocate
    76      #'us-east-1f', # limiting to 3 zones to not overallocate
    77      #'us-east-2a', InsufficientInstanceCapacity for c4.large 2018-05-30
    78      #'us-east-2b', InsufficientInstanceCapacity for c4.large 2018-05-30
    79      #'us-east-2c', InsufficientInstanceCapacity for c4.large 2018-05-30
    80      'us-west-1a',
    81      'us-west-1b',
    82      #'us-west-1c', AZ does not exist, so we're breaking the 3 AZs per region target here
    83      #'us-west-2a', # temporarily removing due to lack of quota #10043
    84      #'us-west-2b', # temporarily removing due to lack of quota #10043
    85      #'us-west-2c', # temporarily removing due to lack of quota #10043
    86  ]
    87  
    88  def test_infra(*paths):
    89      """Return path relative to root of test-infra repo."""
    90      return os.path.join(ORIG_CWD, os.path.dirname(__file__), '..', *paths)
    91  
    92  
    93  def check(*cmd):
    94      """Log and run the command, raising on errors."""
    95      print('Run:', cmd, file=sys.stderr)
    96      subprocess.check_call(cmd)
    97  
    98  
    99  def check_output(*cmd):
   100      """Log and run the command, raising on errors, return output"""
   101      print('Run:', cmd, file=sys.stderr)
   102      return subprocess.check_output(cmd)
   103  
   104  
   105  def check_env(env, *cmd):
   106      """Log and run the command with a specific env, raising on errors."""
   107      print('Environment:', file=sys.stderr)
   108      for key, value in sorted(env.items()):
   109          print('%s=%s' % (key, value), file=sys.stderr)
   110      print('Run:', cmd, file=sys.stderr)
   111      subprocess.check_call(cmd, env=env)
   112  
   113  
   114  def kubekins(tag):
   115      """Return full path to kubekins-e2e:tag."""
   116      return 'gcr.io/k8s-staging-test-infra/kubekins-e2e:%s' % tag
   117  
   118  def parse_env(env):
   119      """Returns (FOO, BAR=MORE) for FOO=BAR=MORE."""
   120      return env.split('=', 1)
   121  
   122  def aws_role_config(profile, arn):
   123      return (('[profile jenkins-assumed-role]\n' +
   124               'role_arn = %s\n' +
   125               'source_profile = %s\n') %
   126              (arn, profile))
   127  
   128  class LocalMode(object):
   129      """Runs e2e tests by calling kubetest."""
   130      def __init__(self, workspace, artifacts):
   131          self.command = 'kubetest'
   132          self.workspace = workspace
   133          self.artifacts = artifacts
   134          self.env = []
   135          self.os_env = []
   136          self.env_files = []
   137          self.add_environment(
   138              'HOME=%s' % workspace,
   139              'WORKSPACE=%s' % workspace,
   140              'PATH=%s' % os.getenv('PATH'),
   141          )
   142  
   143      def add_environment(self, *envs):
   144          """Adds FOO=BAR to the list of environment overrides."""
   145          self.env.extend(parse_env(e) for e in envs)
   146  
   147      def add_os_environment(self, *envs):
   148          """Adds FOO=BAR to the list of os environment overrides."""
   149          self.os_env.extend(parse_env(e) for e in envs)
   150  
   151      def add_file(self, env_file):
   152          """Reads all FOO=BAR lines from env_file."""
   153          with open(env_file) as fp:
   154              for line in fp:
   155                  line = line.rstrip()
   156                  if not line or line.startswith('#'):
   157                      continue
   158                  self.env_files.append(parse_env(line))
   159  
   160      def add_env(self, env):
   161          self.env_files.append(parse_env(env))
   162  
   163      def add_aws_cred(self, priv, pub, cred):
   164          """Sets aws keys and credentials."""
   165          ssh_dir = os.path.join(self.workspace, '.ssh')
   166          if not os.path.isdir(ssh_dir):
   167              os.makedirs(ssh_dir)
   168  
   169          cred_dir = os.path.join(self.workspace, '.aws')
   170          if not os.path.isdir(cred_dir):
   171              os.makedirs(cred_dir)
   172  
   173          aws_ssh = os.path.join(ssh_dir, 'kube_aws_rsa')
   174          aws_pub = os.path.join(ssh_dir, 'kube_aws_rsa.pub')
   175          aws_cred = os.path.join(cred_dir, 'credentials')
   176          shutil.copy(priv, aws_ssh)
   177          shutil.copy(pub, aws_pub)
   178          shutil.copy(cred, aws_cred)
   179  
   180          self.add_environment(
   181              'AWS_SSH_PRIVATE_KEY_FILE=%s' % priv,
   182              'AWS_SSH_PUBLIC_KEY_FILE=%s' % pub,
   183              'AWS_SHARED_CREDENTIALS_FILE=%s' % cred,
   184          )
   185  
   186      def add_aws_role(self, profile, arn):
   187          with open(os.path.join(self.workspace, '.aws', 'config'), 'w') as cfg:
   188              cfg.write(aws_role_config(profile, arn))
   189          self.add_environment('AWS_SDK_LOAD_CONFIG=true')
   190          return 'jenkins-assumed-role'
   191  
   192      def add_gce_ssh(self, priv, pub):
   193          """Copies priv, pub keys to $WORKSPACE/.ssh."""
   194          ssh_dir = os.path.join(self.workspace, '.ssh')
   195          if not os.path.isdir(ssh_dir):
   196              os.makedirs(ssh_dir)
   197  
   198          gce_ssh = os.path.join(ssh_dir, 'google_compute_engine')
   199          gce_pub = os.path.join(ssh_dir, 'google_compute_engine.pub')
   200          shutil.copy(priv, gce_ssh)
   201          shutil.copy(pub, gce_pub)
   202          self.add_environment(
   203              'JENKINS_GCE_SSH_PRIVATE_KEY_FILE=%s' % gce_ssh,
   204              'JENKINS_GCE_SSH_PUBLIC_KEY_FILE=%s' % gce_pub,
   205          )
   206  
   207      @staticmethod
   208      def add_service_account(path):
   209          """Returns path."""
   210          return path
   211  
   212      def add_k8s(self, *a, **kw):
   213          """Add specified k8s.io repos (noop)."""
   214          pass
   215  
   216      def add_aws_runner(self):
   217          """Start with kops-e2e-runner.sh"""
   218          # TODO(Krzyzacy):retire kops-e2e-runner.sh
   219          self.command = os.path.join(self.workspace, 'kops-e2e-runner.sh')
   220  
   221      def start(self, args):
   222          """Starts kubetest."""
   223          print('starts with local mode', file=sys.stderr)
   224          env = {}
   225          env.update(self.os_env)
   226          env.update(self.env_files)
   227          env.update(self.env)
   228          check_env(env, self.command, *args)
   229  
   230  
   231  def cluster_name(cluster, tear_down_previous=False):
   232      """Return or select a cluster name."""
   233      if cluster:
   234          return cluster
   235      # Create a suffix based on the build number and job name.
   236      # This ensures no conflict across runs of different jobs (see #7592).
   237      # For PR jobs, we use PR number instead of build number to ensure the
   238      # name is constant across different runs of the presubmit on the PR.
   239      # This helps clean potentially leaked resources from earlier run that
   240      # could've got evicted midway (see #7673).
   241      job_type = os.getenv('JOB_TYPE')
   242      if job_type == 'batch':
   243          suffix = 'batch-%s' % os.getenv('BUILD_ID', 0)
   244      elif job_type == 'presubmit' and tear_down_previous:
   245          suffix = '%s' % os.getenv('PULL_NUMBER', 0)
   246      else:
   247          suffix = '%s' % os.getenv('BUILD_ID', 0)
   248      if len(suffix) > 10:
   249          suffix = hashlib.md5(suffix.encode('utf-8')).hexdigest()[:10]
   250      job_hash = hashlib.md5(os.getenv('JOB_NAME', '').encode('utf-8')).hexdigest()[:5]
   251      return 'e2e-%s-%s' % (suffix, job_hash)
   252  
   253  
   254  # TODO(krzyzacy): Move this into kubetest
   255  def build_kops(kops, mode):
   256      """Build kops, set kops related envs."""
   257      if not os.path.basename(kops) == 'kops':
   258          raise ValueError(kops)
   259      version = 'pull-' + check_output('git', 'describe', '--always').strip()
   260      job = os.getenv('JOB_NAME', 'pull-kops-e2e-kubernetes-aws')
   261      gcs = 'gs://kops-ci/pulls/%s' % job
   262      gapi = 'https://storage.googleapis.com/kops-ci/pulls/%s' % job
   263      mode.add_environment(
   264          'KOPS_BASE_URL=%s/%s' % (gapi, version),
   265          'GCS_LOCATION=%s' % gcs
   266          )
   267      check('make', 'gcs-publish-ci', 'VERSION=%s' % version, 'GCS_LOCATION=%s' % gcs)
   268  
   269  
   270  def set_up_kops_gce(workspace, args, mode, cluster, runner_args):
   271      """Set up kops on GCE envs."""
   272      for path in [args.gce_ssh, args.gce_pub]:
   273          if not os.path.isfile(os.path.expandvars(path)):
   274              raise IOError(path, os.path.expandvars(path))
   275      mode.add_gce_ssh(args.gce_ssh, args.gce_pub)
   276  
   277      gce_ssh = os.path.join(workspace, '.ssh', 'google_compute_engine')
   278  
   279      zones = args.kops_zones or random.choice([
   280          'us-central1-a',
   281          'us-central1-b',
   282          'us-central1-c',
   283          'us-central1-f',
   284      ])
   285  
   286      runner_args.extend([
   287          '--kops-cluster=%s' % cluster,
   288          '--kops-zones=%s' % zones,
   289          '--kops-state=%s' % args.kops_state_gce,
   290          '--kops-nodes=%s' % args.kops_nodes,
   291          '--kops-ssh-key=%s' % gce_ssh,
   292      ])
   293  
   294  
   295  def set_up_kops_aws(workspace, args, mode, cluster, runner_args):
   296      """Set up aws related envs for kops.  Will replace set_up_aws."""
   297      for path in [args.aws_ssh, args.aws_pub, args.aws_cred]:
   298          if not os.path.isfile(os.path.expandvars(path)):
   299              raise IOError(path, os.path.expandvars(path))
   300      mode.add_aws_cred(args.aws_ssh, args.aws_pub, args.aws_cred)
   301  
   302      aws_ssh = os.path.join(workspace, '.ssh', 'kube_aws_rsa')
   303      profile = args.aws_profile
   304      if args.aws_role_arn:
   305          profile = mode.add_aws_role(profile, args.aws_role_arn)
   306  
   307      # kubetest for kops now support select random regions and zones.
   308      # For initial testing we are not sending in zones when the
   309      # --kops-multiple-zones flag is set.  If the flag is not set then
   310      # we use the older functionality of passing in zones.
   311      if args.kops_multiple_zones:
   312          runner_args.extend(["--kops-multiple-zones"])
   313      else:
   314          # TODO(@chrislovecnm): once we have tested we can remove the zones
   315          # and region logic from this code and have kubetest handle that
   316          # logic
   317          zones = args.kops_zones or random.choice(DEFAULT_AWS_ZONES)
   318          regions = ','.join([zone[:-1] for zone in zones.split(',')])
   319          runner_args.extend(['--kops-zones=%s' % zones])
   320          mode.add_environment(
   321            'KOPS_REGIONS=%s' % regions,
   322          )
   323  
   324      mode.add_environment(
   325        'AWS_PROFILE=%s' % profile,
   326        'AWS_DEFAULT_PROFILE=%s' % profile,
   327      )
   328  
   329      if args.aws_cluster_domain:
   330          cluster = '%s.%s' % (cluster, args.aws_cluster_domain)
   331  
   332      # AWS requires a username (and it varies per-image)
   333      ssh_user = args.kops_ssh_user or 'admin'
   334  
   335      runner_args.extend([
   336          '--kops-cluster=%s' % cluster,
   337          '--kops-state=%s' % args.kops_state,
   338          '--kops-nodes=%s' % args.kops_nodes,
   339          '--kops-ssh-key=%s' % aws_ssh,
   340          '--kops-ssh-user=%s' % ssh_user,
   341      ])
   342  
   343  
   344  def set_up_aws(workspace, args, mode, cluster, runner_args):
   345      """Set up aws related envs.  Legacy; will be replaced by set_up_kops_aws."""
   346      for path in [args.aws_ssh, args.aws_pub, args.aws_cred]:
   347          if not os.path.isfile(os.path.expandvars(path)):
   348              raise IOError(path, os.path.expandvars(path))
   349      mode.add_aws_cred(args.aws_ssh, args.aws_pub, args.aws_cred)
   350  
   351      aws_ssh = os.path.join(workspace, '.ssh', 'kube_aws_rsa')
   352      profile = args.aws_profile
   353      if args.aws_role_arn:
   354          profile = mode.add_aws_role(profile, args.aws_role_arn)
   355  
   356      zones = args.kops_zones or random.choice(DEFAULT_AWS_ZONES)
   357      regions = ','.join([zone[:-1] for zone in zones.split(',')])
   358  
   359      mode.add_environment(
   360        'AWS_PROFILE=%s' % profile,
   361        'AWS_DEFAULT_PROFILE=%s' % profile,
   362        'KOPS_REGIONS=%s' % regions,
   363      )
   364  
   365      if args.aws_cluster_domain:
   366          cluster = '%s.%s' % (cluster, args.aws_cluster_domain)
   367  
   368      # AWS requires a username (and it varies per-image)
   369      ssh_user = args.kops_ssh_user or 'admin'
   370  
   371      runner_args.extend([
   372          '--kops-cluster=%s' % cluster,
   373          '--kops-zones=%s' % zones,
   374          '--kops-state=%s' % args.kops_state,
   375          '--kops-nodes=%s' % args.kops_nodes,
   376          '--kops-ssh-key=%s' % aws_ssh,
   377          '--kops-ssh-user=%s' % ssh_user,
   378      ])
   379      # TODO(krzyzacy):Remove after retire kops-e2e-runner.sh
   380      mode.add_aws_runner()
   381  
   382  def read_gcs_path(gcs_path):
   383      """reads a gcs path (gs://...) by HTTP GET to storage.googleapis.com"""
   384      link = gcs_path.replace('gs://', 'https://storage.googleapis.com/')
   385      loc = urllib.request.urlopen(link).read()
   386      print("Read GCS Path: %s" % loc, file=sys.stderr)
   387      return loc
   388  
   389  def get_shared_gcs_path(gcs_shared, use_shared_build):
   390      """return the shared path for this set of jobs using args and $PULL_REFS."""
   391      build_file = ''
   392      if use_shared_build:
   393          build_file += use_shared_build + '-'
   394      build_file += 'build-location.txt'
   395      return os.path.join(gcs_shared, os.getenv('PULL_REFS', ''), build_file)
   396  
   397  def inject_bazelrc(lines):
   398      if not lines:
   399          return
   400      lines = [l + '\n' for l in lines]
   401      with open('/etc/bazel.bazelrc', 'a') as fp:
   402          fp.writelines(lines)
   403      path = os.path.join(os.getenv('HOME'), '.bazelrc')
   404      with open(path, 'a') as fp:
   405          fp.writelines(lines)
   406  
   407  def main(args):
   408      """Set up env, start kubekins-e2e, handle termination. """
   409      # pylint: disable=too-many-branches,too-many-statements,too-many-locals
   410  
   411      # Rules for env var priority here in docker:
   412      # -e FOO=a -e FOO=b -> FOO=b
   413      # --env-file FOO=a --env-file FOO=b -> FOO=b
   414      # -e FOO=a --env-file FOO=b -> FOO=a(!!!!)
   415      # --env-file FOO=a -e FOO=b -> FOO=b
   416      #
   417      # So if you overwrite FOO=c for a local run it will take precedence.
   418      #
   419  
   420      # Set up workspace/artifacts dir
   421      workspace = os.environ.get('WORKSPACE', os.getcwd())
   422      artifacts = os.environ.get('ARTIFACTS', os.path.join(workspace, '_artifacts'))
   423      if not os.path.isdir(artifacts):
   424          os.makedirs(artifacts)
   425  
   426      inject_bazelrc(args.inject_bazelrc)
   427  
   428      mode = LocalMode(workspace, artifacts)
   429  
   430      for env_file in args.env_file:
   431          mode.add_file(test_infra(env_file))
   432      for env in args.env:
   433          mode.add_env(env)
   434  
   435      # TODO(fejta): remove after next image push
   436      mode.add_environment('KUBETEST_MANUAL_DUMP=y')
   437      if args.dump_before_and_after:
   438          before_dir = os.path.join(mode.artifacts, 'before')
   439          if not os.path.exists(before_dir):
   440              os.makedirs(before_dir)
   441          after_dir = os.path.join(mode.artifacts, 'after')
   442          if not os.path.exists(after_dir):
   443              os.makedirs(after_dir)
   444  
   445          runner_args = [
   446              '--dump-pre-test-logs=%s' % before_dir,
   447              '--dump=%s' % after_dir,
   448              ]
   449      else:
   450          runner_args = [
   451              '--dump=%s' % mode.artifacts,
   452          ]
   453  
   454      if args.service_account:
   455          runner_args.append(
   456              '--gcp-service-account=%s' % mode.add_service_account(args.service_account))
   457  
   458      shared_build_gcs_path = ""
   459      if args.use_shared_build is not None:
   460          # find shared build location from GCS
   461          gcs_path = get_shared_gcs_path(args.gcs_shared, args.use_shared_build)
   462          print('Getting shared build location from: '+gcs_path, file=sys.stderr)
   463          # retry loop for reading the location
   464          attempts_remaining = 12
   465          while True:
   466              attempts_remaining -= 1
   467              try:
   468                  # tell kubetest to extract from this location
   469                  shared_build_gcs_path = read_gcs_path(gcs_path)
   470                  args.kubetest_args.append('--extract=' + shared_build_gcs_path)
   471                  args.build = None
   472                  break
   473              except urllib.error.URLError as err:
   474                  print('Failed to get shared build location: %s' % err, file=sys.stderr)
   475                  if attempts_remaining > 0:
   476                      print('Waiting 5 seconds and retrying...', file=sys.stderr)
   477                      time.sleep(5)
   478                  else:
   479                      raise RuntimeError('Failed to get shared build location too many times!')
   480  
   481      elif args.build is not None:
   482          if args.build == '':
   483              # Empty string means --build was passed without any arguments;
   484              # if --build wasn't passed, args.build would be None
   485              runner_args.append('--build')
   486          else:
   487              runner_args.append('--build=%s' % args.build)
   488          k8s = os.getcwd()
   489          if not os.path.basename(k8s) == 'kubernetes':
   490              raise ValueError(k8s)
   491          mode.add_k8s(os.path.dirname(k8s), 'kubernetes', 'release')
   492  
   493      if args.build_federation is not None:
   494          if args.build_federation == '':
   495              runner_args.append('--build-federation')
   496          else:
   497              runner_args.append('--build-federation=%s' % args.build_federation)
   498          fed = os.getcwd()
   499          if not os.path.basename(fed) == 'federation':
   500              raise ValueError(fed)
   501          mode.add_k8s(os.path.dirname(fed), 'federation', 'release')
   502  
   503      if args.kops_build:
   504          build_kops(os.getcwd(), mode)
   505  
   506      if args.stage is not None:
   507          runner_args.append('--stage=%s' % args.stage)
   508          if args.aws:
   509              for line in check_output('hack/print-workspace-status.sh').split('\n'):
   510                  if 'gitVersion' in line:
   511                      _, version = line.strip().split(' ')
   512                      break
   513              else:
   514                  raise ValueError('kubernetes version not found in workspace status')
   515              runner_args.append('--kops-kubernetes-version=%s/%s' % (
   516                  args.stage.replace('gs://', 'https://storage.googleapis.com/'),
   517                  version))
   518  
   519      # TODO(fejta): move these out of this file
   520      if args.up == 'true':
   521          runner_args.append('--up')
   522      if args.down == 'true':
   523          runner_args.append('--down')
   524      if args.test == 'true':
   525          runner_args.append('--test')
   526  
   527      # Passthrough some args to kubetest
   528      if args.deployment:
   529          runner_args.append('--deployment=%s' % args.deployment)
   530      if args.provider:
   531          runner_args.append('--provider=%s' % args.provider)
   532  
   533      cluster = cluster_name(args.cluster, args.tear_down_previous)
   534      runner_args.append('--cluster=%s' % cluster)
   535      runner_args.append('--gcp-network=%s' % cluster)
   536      runner_args.extend(args.kubetest_args)
   537  
   538      if args.use_logexporter:
   539          runner_args.append('--logexporter-gcs-path=%s' % args.logexporter_gcs_path)
   540  
   541      if args.aws:
   542          # Legacy - prefer passing --deployment=kops, --provider=aws,
   543          # which does not use kops-e2e-runner.sh
   544          set_up_aws(mode.workspace, args, mode, cluster, runner_args)
   545      elif args.deployment == 'kops' and args.provider == 'aws':
   546          set_up_kops_aws(mode.workspace, args, mode, cluster, runner_args)
   547      elif args.deployment == 'kops' and args.provider == 'gce':
   548          set_up_kops_gce(mode.workspace, args, mode, cluster, runner_args)
   549      elif args.deployment != 'kind' and args.gce_ssh:
   550          mode.add_gce_ssh(args.gce_ssh, args.gce_pub)
   551  
   552      # TODO(fejta): delete this?
   553      mode.add_os_environment(*(
   554          '%s=%s' % (k, v) for (k, v) in list(os.environ.items())))
   555  
   556      mode.add_environment(
   557        # Boilerplate envs
   558        # Skip gcloud update checking
   559        'CLOUDSDK_COMPONENT_MANAGER_DISABLE_UPDATE_CHECK=true',
   560        # Use default component update behavior
   561        'CLOUDSDK_EXPERIMENTAL_FAST_COMPONENT_UPDATE=false',
   562        # AWS
   563        'KUBE_AWS_INSTANCE_PREFIX=%s' % cluster,
   564        # GCE
   565        'INSTANCE_PREFIX=%s' % cluster,
   566        'KUBE_GCE_INSTANCE_PREFIX=%s' % cluster,
   567      )
   568  
   569      mode.start(runner_args)
   570  
   571  def create_parser():
   572      """Create argparser."""
   573      parser = argparse.ArgumentParser()
   574      parser.add_argument(
   575          '--env-file', default=[], action="append",
   576          help='Job specific environment file')
   577      parser.add_argument(
   578          '--env', default=[], action="append",
   579          help='Job specific environment setting ' +
   580          '(usage: "--env=VAR=SETTING" will set VAR to SETTING).')
   581      parser.add_argument(
   582          '--gce-ssh',
   583          default=os.environ.get('JENKINS_GCE_SSH_PRIVATE_KEY_FILE'),
   584          help='Path to .ssh/google_compute_engine keys')
   585      parser.add_argument(
   586          '--gce-pub',
   587          default=os.environ.get('JENKINS_GCE_SSH_PUBLIC_KEY_FILE'),
   588          help='Path to pub gce ssh key')
   589      parser.add_argument(
   590          '--service-account',
   591          default=os.environ.get('GOOGLE_APPLICATION_CREDENTIALS'),
   592          help='Path to service-account.json')
   593      parser.add_argument(
   594          '--build', nargs='?', default=None, const='',
   595          help='Build kubernetes binaries if set, optionally specifying strategy')
   596      parser.add_argument(
   597          '--inject-bazelrc', default=[], action='append',
   598          help='Inject /etc/bazel.bazelrc and ~/.bazelrc lines')
   599      parser.add_argument(
   600          '--build-federation', nargs='?', default=None, const='',
   601          help='Build federation binaries if set, optionally specifying strategy')
   602      parser.add_argument(
   603          '--use-shared-build', nargs='?', default=None, const='',
   604          help='Use prebuilt kubernetes binaries if set, optionally specifying strategy')
   605      parser.add_argument(
   606          '--gcs-shared',
   607          default='gs://kubernetes-jenkins/shared-results/',
   608          help='Get shared build from this bucket')
   609      parser.add_argument(
   610          '--cluster', default='bootstrap-e2e', help='Name of the cluster')
   611      parser.add_argument(
   612          '--stage', default=None, help='Stage release to GCS path provided')
   613      parser.add_argument(
   614          '--test', default='true', help='If we need to run any actual test within kubetest')
   615      parser.add_argument(
   616          '--down', default='true', help='If we need to tear down the e2e cluster')
   617      parser.add_argument(
   618          '--up', default='true', help='If we need to bring up a e2e cluster')
   619      parser.add_argument(
   620          '--tear-down-previous', action='store_true',
   621          help='If we need to tear down previous e2e cluster')
   622      parser.add_argument(
   623          '--use-logexporter',
   624          action='store_true',
   625          help='If we need to use logexporter tool to upload logs from nodes to GCS directly')
   626      parser.add_argument(
   627          '--logexporter-gcs-path',
   628          default=os.environ.get('GCS_ARTIFACTS_DIR',''),
   629          help='GCS path where logexporter tool will upload logs if enabled')
   630      parser.add_argument(
   631          '--kubetest_args',
   632          action='append',
   633          default=[],
   634          help='Send unrecognized args directly to kubetest')
   635      parser.add_argument(
   636          '--dump-before-and-after', action='store_true',
   637          help='Dump artifacts from both before and after the test run')
   638  
   639  
   640      # kops & aws
   641      # TODO(justinsb): replace with --provider=aws --deployment=kops
   642      parser.add_argument(
   643          '--aws', action='store_true', help='E2E job runs in aws')
   644      parser.add_argument(
   645          '--aws-profile',
   646          default=(
   647              os.environ.get('AWS_PROFILE') or
   648              os.environ.get('AWS_DEFAULT_PROFILE') or
   649              'default'
   650          ),
   651          help='Profile within --aws-cred to use')
   652      parser.add_argument(
   653          '--aws-role-arn',
   654          default=os.environ.get('KOPS_E2E_ROLE_ARN'),
   655          help='Use --aws-profile to run as --aws-role-arn if set')
   656      parser.add_argument(
   657          '--aws-ssh',
   658          default=os.environ.get('AWS_SSH_PRIVATE_KEY_FILE'),
   659          help='Path to private aws ssh keys')
   660      parser.add_argument(
   661          '--aws-pub',
   662          default=os.environ.get('AWS_SSH_PUBLIC_KEY_FILE'),
   663          help='Path to pub aws ssh key')
   664      parser.add_argument(
   665          '--aws-cred',
   666          default=os.environ.get('AWS_SHARED_CREDENTIALS_FILE'),
   667          help='Path to aws credential file')
   668      parser.add_argument(
   669          '--aws-cluster-domain', help='Domain of the aws cluster for aws-pr jobs')
   670      parser.add_argument(
   671          '--kops-nodes', default=4, type=int, help='Number of nodes to start')
   672      parser.add_argument(
   673          '--kops-ssh-user', default='',
   674          help='Username for ssh connections to instances')
   675      parser.add_argument(
   676          '--kops-state', default='s3://k8s-kops-prow/',
   677          help='Name of the aws state storage')
   678      parser.add_argument(
   679          '--kops-state-gce', default='gs://k8s-kops-gce/',
   680          help='Name of the kops state storage for GCE')
   681      parser.add_argument(
   682          '--kops-zones', help='Comma-separated list of zones else random choice')
   683      parser.add_argument(
   684          '--kops-build', action='store_true', help='If we need to build kops locally')
   685      parser.add_argument(
   686          '--kops-multiple-zones', action='store_true', help='Use multiple zones')
   687  
   688  
   689      # kubetest flags that also trigger behaviour here
   690      parser.add_argument(
   691          '--provider', help='provider flag as used by kubetest')
   692      parser.add_argument(
   693          '--deployment', help='deployment flag as used by kubetest')
   694  
   695      return parser
   696  
   697  
   698  def parse_args(args=None):
   699      """Return args, adding unrecognized args to kubetest_args."""
   700      parser = create_parser()
   701      args, extra = parser.parse_known_args(args)
   702      args.kubetest_args += extra
   703  
   704      if args.aws or args.provider == 'aws':
   705          # If aws keys are missing, try to fetch from HOME dir
   706          if not args.aws_ssh or not args.aws_pub or not args.aws_cred:
   707              home = os.environ.get('HOME')
   708              if not home:
   709                  raise ValueError('HOME dir not set!')
   710              if not args.aws_ssh:
   711                  args.aws_ssh = '%s/.ssh/kube_aws_rsa' % home
   712                  print('-aws-ssh key not set. Defaulting to %s' % args.aws_ssh, file=sys.stderr)
   713              if not args.aws_pub:
   714                  args.aws_pub = '%s/.ssh/kube_aws_rsa.pub' % home
   715                  print('--aws-pub key not set. Defaulting to %s' % args.aws_pub, file=sys.stderr)
   716              if not args.aws_cred:
   717                  args.aws_cred = '%s/.aws/credentials' % home
   718                  print('--aws-cred not set. Defaulting to %s' % args.aws_cred, file=sys.stderr)
   719      return args
   720  
   721  
   722  if __name__ == '__main__':
   723      main(parse_args())