github.com/shashidharatd/test-infra@v0.0.0-20171006011030-71304e1ca560/scenarios/kubernetes_e2e.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  # 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 signal
    29  import subprocess
    30  import sys
    31  import tempfile
    32  import traceback
    33  import urllib2
    34  import time
    35  
    36  ORIG_CWD = os.getcwd()  # Checkout changes cwd
    37  
    38  # Note: This variable is managed by experiment/bump_e2e_image.sh.
    39  DEFAULT_KUBEKINS_TAG = 'v20171002-aae4252c'
    40  
    41  def test_infra(*paths):
    42      """Return path relative to root of test-infra repo."""
    43      return os.path.join(ORIG_CWD, os.path.dirname(__file__), '..', *paths)
    44  
    45  
    46  def check(*cmd):
    47      """Log and run the command, raising on errors."""
    48      print >>sys.stderr, 'Run:', cmd
    49      subprocess.check_call(cmd)
    50  
    51  
    52  def check_output(*cmd):
    53      """Log and run the command, raising on errors, return output"""
    54      print >>sys.stderr, 'Run:', cmd
    55      return subprocess.check_output(cmd)
    56  
    57  
    58  def check_env(env, *cmd):
    59      """Log and run the command with a specific env, raising on errors."""
    60      print >>sys.stderr, 'Environment:'
    61      for key, value in env.items():
    62          print >>sys.stderr, '%s=%s' % (key, value)
    63      print >>sys.stderr, 'Run:', cmd
    64      subprocess.check_call(cmd, env=env)
    65  
    66  
    67  def kubekins(tag):
    68      """Return full path to kubekins-e2e:tag."""
    69      return 'gcr.io/k8s-testimages/kubekins-e2e:%s' % tag
    70  
    71  def parse_env(env):
    72      """Returns (FOO, BAR=MORE) for FOO=BAR=MORE."""
    73      return env.split('=', 1)
    74  
    75  def aws_role_config(profile, arn):
    76      return (('[profile jenkins-assumed-role]\n' +
    77               'role_arn = %s\n' +
    78               'source_profile = %s\n') %
    79              (arn, profile))
    80  
    81  def kubeadm_version(mode, shared_build_gcs_path):
    82      """Return string to use for kubeadm version, given the job's mode (ci/pull/periodic)."""
    83      version = ''
    84      if mode in ['ci', 'periodic']:
    85          # This job only runs against the kubernetes repo, and bootstrap.py leaves the
    86          # current working directory at the repository root. Grab the SCM_REVISION so we
    87          # can use the .debs built during the bazel-build job that should have already
    88          # succeeded.
    89          status = re.search(
    90              r'STABLE_BUILD_SCM_REVISION ([^\n]+)',
    91              check_output('hack/print-workspace-status.sh')
    92          )
    93          if not status:
    94              raise ValueError('STABLE_BUILD_SCM_REVISION not found')
    95          version = status.group(1)
    96  
    97          # Work-around for release-1.6 jobs, which still upload debs to an older
    98          # location (without os/arch prefixes).
    99          # TODO(pipejakob): remove this when we no longer support 1.6.x.
   100          if version.startswith('v1.6.'):
   101              return 'gs://kubernetes-release-dev/bazel/%s/build/debs/' % version
   102  
   103          # The path given here should match jobs/ci-kubernetes-bazel-build.sh
   104          return 'gs://kubernetes-release-dev/bazel/%s/bin/linux/amd64/' % version
   105  
   106      elif mode == 'pull':
   107          # The format of shared_build_gcs_path looks like:
   108          # gs://kubernetes-release-dev/bazel/<git-describe-output>
   109          # Add bin/linux/amd64 yet to that path so it points to the dir with the debs
   110          return '%s/bin/linux/amd64/' % shared_build_gcs_path
   111  
   112      elif mode == 'stable':
   113          # This job need not run against the kubernetes repo and uses the stable version
   114          # of kubeadm packages. This mode may be desired when kubeadm itself is not the
   115          # SUT (System Under Test).
   116          return 'stable'
   117  
   118      else:
   119          raise ValueError("Unknown kubeadm mode given: %s" % mode)
   120  
   121  class LocalMode(object):
   122      """Runs e2e tests by calling kubetest."""
   123      def __init__(self, workspace, artifacts):
   124          self.command = 'kubetest'
   125          self.workspace = workspace
   126          self.artifacts = artifacts
   127          self.env = []
   128          self.os_env = []
   129          self.env_files = []
   130          self.add_environment(
   131              'HOME=%s' % workspace,
   132              'WORKSPACE=%s' % workspace,
   133              'PATH=%s' % os.getenv('PATH'),
   134          )
   135  
   136      def add_environment(self, *envs):
   137          """Adds FOO=BAR to the list of environment overrides."""
   138          self.env.extend(parse_env(e) for e in envs)
   139  
   140      def add_os_environment(self, *envs):
   141          """Adds FOO=BAR to the list of os environment overrides."""
   142          self.os_env.extend(parse_env(e) for e in envs)
   143  
   144      def add_file(self, env_file):
   145          """Reads all FOO=BAR lines from env_file."""
   146          with open(env_file) as fp:
   147              for line in fp:
   148                  line = line.rstrip()
   149                  if not line or line.startswith('#'):
   150                      continue
   151                  self.env_files.append(parse_env(line))
   152  
   153      def add_env(self, env):
   154          self.env_files.append(parse_env(env))
   155  
   156      def add_aws_cred(self, priv, pub, cred):
   157          """Sets aws keys and credentials."""
   158          ssh_dir = os.path.join(self.workspace, '.ssh')
   159          if not os.path.isdir(ssh_dir):
   160              os.makedirs(ssh_dir)
   161  
   162          cred_dir = os.path.join(self.workspace, '.aws')
   163          if not os.path.isdir(cred_dir):
   164              os.makedirs(cred_dir)
   165  
   166          aws_ssh = os.path.join(ssh_dir, 'kube_aws_rsa')
   167          aws_pub = os.path.join(ssh_dir, 'kube_aws_rsa.pub')
   168          aws_cred = os.path.join(cred_dir, 'credentials')
   169          shutil.copy(priv, aws_ssh)
   170          shutil.copy(pub, aws_pub)
   171          shutil.copy(cred, aws_cred)
   172  
   173          self.add_environment(
   174              'JENKINS_AWS_SSH_PRIVATE_KEY_FILE=%s' % priv,
   175              'JENKINS_AWS_SSH_PUBLIC_KEY_FILE=%s' % pub,
   176              'JENKINS_AWS_CREDENTIALS_FILE=%s' % cred,
   177          )
   178  
   179      def add_aws_role(self, profile, arn):
   180          with open(os.path.join(self.workspace, '.aws', 'config'), 'w') as cfg:
   181              cfg.write(aws_role_config(profile, arn))
   182          self.add_environment('AWS_SDK_LOAD_CONFIG=true')
   183          return 'jenkins-assumed-role'
   184  
   185      def add_gce_ssh(self, priv, pub):
   186          """Copies priv, pub keys to $WORKSPACE/.ssh."""
   187          ssh_dir = os.path.join(self.workspace, '.ssh')
   188          if not os.path.isdir(ssh_dir):
   189              os.makedirs(ssh_dir)
   190  
   191          gce_ssh = os.path.join(ssh_dir, 'google_compute_engine')
   192          gce_pub = os.path.join(ssh_dir, 'google_compute_engine.pub')
   193          shutil.copy(priv, gce_ssh)
   194          shutil.copy(pub, gce_pub)
   195          self.add_environment(
   196              'JENKINS_GCE_SSH_PRIVATE_KEY_FILE=%s' % gce_ssh,
   197              'JENKINS_GCE_SSH_PUBLIC_KEY_FILE=%s' % gce_pub,
   198          )
   199  
   200      @staticmethod
   201      def add_service_account(path):
   202          """Returns path."""
   203          return path
   204  
   205      def add_k8s(self, *a, **kw):
   206          """Add specified k8s.io repos (noop)."""
   207          pass
   208  
   209      def use_latest_image(self, image_family, image_project):
   210          """Gets the latest image from the image_family in the image_project."""
   211          out = check_output(
   212              'gcloud', 'compute', 'images', 'describe-from-family',
   213              image_family, '--project=%s' % image_project)
   214          latest_image = next(
   215              (line[6:].strip() for line in out.split('\n') if (
   216                  line.startswith('name: '))),
   217              None)
   218          if not latest_image:
   219              raise ValueError(
   220                  'Failed to get the latest image from family %s in project %s' % (
   221                      image_family, image_project))
   222          # TODO(yguo0905): Support this in GKE.
   223          self.add_environment(
   224              'KUBE_GCE_NODE_IMAGE=%s' % latest_image,
   225              'KUBE_GCE_NODE_PROJECT=%s' % image_project)
   226          print >>sys.stderr, 'Set KUBE_GCE_NODE_IMAGE=%s' % latest_image
   227          print >>sys.stderr, 'Set KUBE_GCE_NODE_PROJECT=%s' % image_project
   228  
   229      def add_aws_runner(self):
   230          """Start with kops-e2e-runner.sh"""
   231          # TODO(Krzyzacy):retire kops-e2e-runner.sh
   232          self.command = os.path.join(self.workspace, 'kops-e2e-runner.sh')
   233  
   234      def start(self, args):
   235          """Starts kubetest."""
   236          print >>sys.stderr, 'starts with local mode'
   237          env = {}
   238          env.update(self.os_env)
   239          env.update(self.env_files)
   240          env.update(self.env)
   241          check_env(env, self.command, *args)
   242  
   243  
   244  class DockerMode(object):
   245      """Runs e2e tests via docker run kubekins-e2e."""
   246      def __init__(self, container, artifacts, sudo, tag, mount_paths):
   247          self.tag = tag
   248          try:  # Pull a newer version if one exists
   249              check('docker', 'pull', kubekins(tag))
   250          except subprocess.CalledProcessError:
   251              pass
   252  
   253          print 'Starting %s...' % container
   254  
   255          self.workspace = '/workspace'
   256          self.container = container
   257          self.local_artifacts = artifacts
   258          self.artifacts = os.path.join(self.workspace, '_artifacts')
   259          self.cmd = [
   260              'docker', 'run', '--rm',
   261              '--name=%s' % container,
   262              '-v', '%s:%s' % (artifacts, self.artifacts),
   263              '-v', '/etc/localtime:/etc/localtime:ro',
   264          ]
   265          for path in mount_paths or []:
   266              self.cmd.extend(['-v', path])
   267  
   268          if sudo:
   269              self.cmd.extend(['-v', '/var/run/docker.sock:/var/run/docker.sock'])
   270          self.add_env('HOME=%s' % self.workspace)
   271          self.add_env('WORKSPACE=%s' % self.workspace)
   272          self.cmd.append(
   273              '--entrypoint=/workspace/kubetest'
   274          )
   275  
   276      def add_environment(self, *envs):
   277          """Adds FOO=BAR to the -e list for docker.
   278  
   279          Host-specific environment variables are ignored."""
   280          # TODO(krzyzacy) change this to a whitelist?
   281          docker_env_ignore = [
   282              'GOOGLE_APPLICATION_CREDENTIALS',
   283              'GOPATH',
   284              'GOROOT',
   285              'HOME',
   286              'PATH',
   287              'PWD',
   288              'WORKSPACE'
   289          ]
   290          for env in envs:
   291              key, _value = parse_env(env)
   292              if key in docker_env_ignore:
   293                  print >>sys.stderr, 'Skipping environment variable %s' % env
   294              else:
   295                  self.add_env(env)
   296  
   297      def add_os_environment(self, *envs):
   298          """Adds os envs as FOO=BAR to the -e list for docker."""
   299          self.add_environment(*envs)
   300  
   301      def add_file(self, env_file):
   302          """Adds the file to the --env-file list."""
   303          self.cmd.extend(['--env-file', env_file])
   304  
   305      def add_env(self, env):
   306          """Adds a single environment variable to the -e list for docker.
   307  
   308          Does not check against any blacklists."""
   309          self.cmd.extend(['-e', env])
   310  
   311      def add_k8s(self, k8s, *repos):
   312          """Add the specified k8s.io repos into container."""
   313          for repo in repos:
   314              self.cmd.extend([
   315                  '-v', '%s/%s:/go/src/k8s.io/%s' % (k8s, repo, repo)])
   316  
   317      def add_aws_cred(self, priv, pub, cred):
   318          """Mounts aws keys/creds inside the container."""
   319          aws_ssh = os.path.join(self.workspace, '.ssh', 'kube_aws_rsa')
   320          aws_pub = '%s.pub' % aws_ssh
   321          aws_cred = os.path.join(self.workspace, '.aws', 'credentials')
   322  
   323          self.cmd.extend([
   324            '-v', '%s:%s:ro' % (priv, aws_ssh),
   325            '-v', '%s:%s:ro' % (pub, aws_pub),
   326            '-v', '%s:%s:ro' % (cred, aws_cred),
   327          ])
   328  
   329      def add_aws_role(self, profile, arn):
   330          with tempfile.NamedTemporaryFile(prefix='aws-config', delete=False) as cfg:
   331              cfg.write(aws_role_config(profile, arn))
   332              self.cmd.extend([
   333                  '-v', '%s:%s:ro' % (os.path.join(self.workspace, '.aws', 'config'), cfg.name),
   334                  '-e', 'AWS_SDK_LOAD_CONFIG=true',
   335              ])
   336          return 'jenkins-assumed-role'
   337  
   338      def add_aws_runner(self):
   339          """Run kops_aws_runner for kops-aws jobs."""
   340          self.cmd.append(
   341            '--entrypoint=%s/kops-e2e-runner.sh' % self.workspace
   342          )
   343  
   344      def add_gce_ssh(self, priv, pub):
   345          """Mounts priv and pub inside the container."""
   346          gce_ssh = os.path.join(self.workspace, '.ssh', 'google_compute_engine')
   347          gce_pub = '%s.pub' % gce_ssh
   348          self.cmd.extend([
   349            '-v', '%s:%s:ro' % (priv, gce_ssh),
   350            '-v', '%s:%s:ro' % (pub, gce_pub),
   351            '-e', 'JENKINS_GCE_SSH_PRIVATE_KEY_FILE=%s' % gce_ssh,
   352            '-e', 'JENKINS_GCE_SSH_PUBLIC_KEY_FILE=%s' % gce_pub])
   353  
   354      def add_service_account(self, path):
   355          """Mounts path at /service-account.json inside the container."""
   356          service = '/service-account.json'
   357          self.cmd.extend(['-v', '%s:%s:ro' % (path, service)])
   358          return service
   359  
   360      def start(self, args):
   361          """Runs kubetest inside a docker container."""
   362          print >>sys.stderr, 'starts with docker mode'
   363          cmd = list(self.cmd)
   364          cmd.append(kubekins(self.tag))
   365          cmd.extend(args)
   366          signal.signal(signal.SIGTERM, self.sig_handler)
   367          signal.signal(signal.SIGINT, self.sig_handler)
   368          try:
   369              check(*cmd)
   370          finally:  # Ensure docker files are readable by bootstrap
   371              if not os.path.isdir(self.local_artifacts):  # May not exist
   372                  pass
   373              try:
   374                  check('sudo', 'chmod', '-R', 'o+r', self.local_artifacts)
   375              except subprocess.CalledProcessError:  # fails outside CI
   376                  traceback.print_exc()
   377  
   378      def sig_handler(self, _signo, _frame):
   379          """Stops container upon receive signal.SIGTERM and signal.SIGINT."""
   380          print >>sys.stderr, 'docker stop (signo=%s, frame=%s)' % (_signo, _frame)
   381          check('docker', 'stop', self.container)
   382  
   383  
   384  def cluster_name(cluster, build):
   385      """Return or select a cluster name."""
   386      if cluster:
   387          return cluster
   388      if len(build) < 20:
   389          return 'e2e-%s' % build
   390      return 'e2e-%s' % hashlib.md5(build).hexdigest()[:10]
   391  
   392  
   393  # TODO(krzyzacy): Move this into kubetest
   394  def build_kops(kops, mode):
   395      """Build kops, set kops related envs."""
   396      if not os.path.basename(kops) == 'kops':
   397          raise ValueError(kops)
   398      version = 'pull-' + check_output('git', 'describe', '--always').strip()
   399      job = os.getenv('JOB_NAME', 'pull-kops-e2e-kubernetes-aws')
   400      gcs = 'gs://kops-ci/pulls/%s' % job
   401      gapi = 'https://storage.googleapis.com/kops-ci/pulls/%s' % job
   402      mode.add_environment(
   403          'KOPS_BASE_URL=%s/%s' % (gapi, version),
   404          'GCS_LOCATION=%s' % gcs
   405          )
   406      check('make', 'gcs-publish-ci', 'VERSION=%s' % version, 'GCS_LOCATION=%s' % gcs)
   407  
   408  
   409  def set_up_aws(workspace, args, mode, cluster, runner_args):
   410      """Set up aws related envs."""
   411      for path in [args.aws_ssh, args.aws_pub, args.aws_cred]:
   412          if not os.path.isfile(os.path.expandvars(path)):
   413              raise IOError(path, os.path.expandvars(path))
   414      mode.add_aws_cred(args.aws_ssh, args.aws_pub, args.aws_cred)
   415  
   416      aws_ssh = os.path.join(workspace, '.ssh', 'kube_aws_rsa')
   417      profile = args.aws_profile
   418      if args.aws_role_arn:
   419          profile = mode.add_aws_role(profile, args.aws_role_arn)
   420  
   421      zones = args.kops_zones or random.choice([
   422          'us-west-1a',
   423          'us-west-1c',
   424          'us-west-2a',
   425          'us-west-2b',
   426          'us-east-1a',
   427          'us-east-1d',
   428          'us-east-2a',
   429          'us-east-2b',
   430      ])
   431      regions = ','.join([zone[:-1] for zone in zones.split(',')])
   432  
   433      mode.add_environment(
   434        'AWS_PROFILE=%s' % profile,
   435        'AWS_DEFAULT_PROFILE=%s' % profile,
   436        'KOPS_REGIONS=%s' % regions,
   437      )
   438  
   439      if args.aws_cluster_domain:
   440          cluster = '%s.%s' % (cluster, args.aws_cluster_domain)
   441  
   442      runner_args.extend([
   443          '--kops-cluster=%s' % cluster,
   444          '--kops-zones=%s' % zones,
   445          '--kops-state=%s' % args.kops_state,
   446          '--kops-nodes=%s' % args.kops_nodes,
   447          '--kops-ssh-key=%s' % aws_ssh,
   448      ])
   449      # TODO(krzyzacy):Remove after retire kops-e2e-runner.sh
   450      mode.add_aws_runner()
   451  
   452  def read_gcs_path(gcs_path):
   453      """reads a gcs path (gs://...) by GETing storage.googleapis.com"""
   454      link = gcs_path.replace('gs://', 'https://storage.googleapis.com/')
   455      loc = urllib2.urlopen(link).read()
   456      print >>sys.stderr, "Read GCS Path: %s" % loc
   457      return loc
   458  
   459  def get_shared_gcs_path(gcs_shared, use_shared_build):
   460      """return the shared path for this set of jobs using args and $PULL_REFS."""
   461      build_file = ''
   462      if use_shared_build:
   463          build_file += use_shared_build + '-'
   464      build_file += 'build-location.txt'
   465      return os.path.join(gcs_shared, os.getenv('PULL_REFS', ''), build_file)
   466  
   467  def main(args):
   468      """Set up env, start kubekins-e2e, handle termination. """
   469      # pylint: disable=too-many-branches,too-many-statements,too-many-locals
   470  
   471      # Rules for env var priority here in docker:
   472      # -e FOO=a -e FOO=b -> FOO=b
   473      # --env-file FOO=a --env-file FOO=b -> FOO=b
   474      # -e FOO=a --env-file FOO=b -> FOO=a(!!!!)
   475      # --env-file FOO=a -e FOO=b -> FOO=b
   476      #
   477      # So if you overwrite FOO=c for a local run it will take precedence.
   478      #
   479  
   480      # Set up workspace/artifacts dir
   481      workspace = os.environ.get('WORKSPACE', os.getcwd())
   482      artifacts = os.path.join(workspace, '_artifacts')
   483      if not os.path.isdir(artifacts):
   484          os.makedirs(artifacts)
   485  
   486      container = '%s-%s' % (os.environ.get('JOB_NAME'), os.environ.get('BUILD_NUMBER'))
   487      if args.mode == 'docker':
   488          sudo = args.docker_in_docker or args.build is not None
   489          mode = DockerMode(container, artifacts, sudo, args.tag, args.mount_paths)
   490      elif args.mode == 'local':
   491          mode = LocalMode(workspace, artifacts)  # pylint: disable=bad-option-value
   492      else:
   493          raise ValueError(args.mode)
   494  
   495      for env_file in args.env_file:
   496          mode.add_file(test_infra(env_file))
   497      for env in args.env:
   498          mode.add_env(env)
   499  
   500      # TODO(fejta): remove after next image push
   501      mode.add_environment('KUBETEST_MANUAL_DUMP=y')
   502      runner_args = [
   503          '-v',
   504          '--dump=%s' % mode.artifacts,
   505      ]
   506  
   507      if args.service_account:
   508          runner_args.append(
   509              '--gcp-service-account=%s' % mode.add_service_account(args.service_account))
   510  
   511      shared_build_gcs_path = ""
   512      if args.use_shared_build is not None:
   513          # find shared build location from GCS
   514          gcs_path = get_shared_gcs_path(args.gcs_shared, args.use_shared_build)
   515          print >>sys.stderr, 'Getting shared build location from: '+gcs_path
   516          # retry loop for reading the location
   517          attempts_remaining = 12
   518          while True:
   519              attempts_remaining -= 1
   520              try:
   521                  # tell kubetest to extract from this location
   522                  shared_build_gcs_path = read_gcs_path(gcs_path)
   523                  args.kubetest_args.append('--extract=' + shared_build_gcs_path)
   524                  args.build = None
   525                  break
   526              except urllib2.URLError as err:
   527                  print >>sys.stderr, 'Failed to get shared build location: %s' % err
   528                  if attempts_remaining > 0:
   529                      print >>sys.stderr, 'Waiting 5 seconds and retrying...'
   530                      time.sleep(5)
   531                  else:
   532                      raise RuntimeError('Failed to get shared build location too many times!')
   533  
   534      elif args.build is not None:
   535          if args.build == '':
   536              # Empty string means --build was passed without any arguments;
   537              # if --build wasn't passed, args.build would be None
   538              runner_args.append('--build')
   539          else:
   540              runner_args.append('--build=%s' % args.build)
   541          k8s = os.getcwd()
   542          if not os.path.basename(k8s) == 'kubernetes':
   543              raise ValueError(k8s)
   544          mode.add_k8s(os.path.dirname(k8s), 'kubernetes', 'release')
   545  
   546      if args.kops_build:
   547          build_kops(os.getcwd(), mode)
   548  
   549      if args.stage is not None:
   550          runner_args.append('--stage=%s' % args.stage)
   551          if args.aws:
   552              for line in check_output('hack/print-workspace-status.sh').split('\n'):
   553                  if 'gitVersion' in line:
   554                      _, version = line.strip().split(' ')
   555                      break
   556              else:
   557                  raise ValueError('kubernetes version not found in workspace status')
   558              runner_args.append('--kops-kubernetes-version=%s/%s' % (
   559                  args.stage.replace('gs://', 'https://storage.googleapis.com/'),
   560                  version))
   561  
   562      # TODO(fejta): move these out of this file
   563      if args.up == 'true':
   564          runner_args.append('--up')
   565      if args.down == 'true':
   566          runner_args.append('--down')
   567      if args.test == 'true':
   568          runner_args.append('--test')
   569  
   570      cluster = cluster_name(args.cluster, os.getenv('BUILD_NUMBER', 0))
   571      runner_args.append('--cluster=%s' % cluster)
   572      runner_args.append('--gcp-network=%s' % cluster)
   573      runner_args.extend(args.kubetest_args)
   574  
   575      if args.use_logexporter:
   576          # TODO(fejta): Take the below value through a flag instead of env var.
   577          runner_args.append('--logexporter-gcs-path=%s' % os.environ.get('GCS_ARTIFACTS_DIR', ''))
   578  
   579      if args.kubeadm:
   580          version = kubeadm_version(args.kubeadm, shared_build_gcs_path)
   581          runner_args.extend([
   582              '--kubernetes-anywhere-path=%s' % os.path.join(workspace, 'kubernetes-anywhere'),
   583              '--kubernetes-anywhere-phase2-provider=kubeadm',
   584              '--kubernetes-anywhere-cluster=%s' % cluster,
   585              '--kubernetes-anywhere-kubeadm-version=%s' % version,
   586          ])
   587  
   588          if args.kubeadm == "pull":
   589              # If this is a pull job; the kubelet version should equal
   590              # the kubeadm version here: we should use debs from the PR build
   591              runner_args.extend([
   592                  '--kubernetes-anywhere-kubelet-version=%s' % version,
   593              ])
   594  
   595      if args.aws:
   596          set_up_aws(mode.workspace, args, mode, cluster, runner_args)
   597      elif args.gce_ssh:
   598          mode.add_gce_ssh(args.gce_ssh, args.gce_pub)
   599  
   600      # TODO(fejta): delete this?
   601      mode.add_os_environment(*(
   602          '%s=%s' % (k, v) for (k, v) in os.environ.items()))
   603  
   604      mode.add_environment(
   605        # Boilerplate envs
   606        # Skip gcloud update checking
   607        'CLOUDSDK_COMPONENT_MANAGER_DISABLE_UPDATE_CHECK=true',
   608        # Use default component update behavior
   609        'CLOUDSDK_EXPERIMENTAL_FAST_COMPONENT_UPDATE=false',
   610        # AWS
   611        'KUBE_AWS_INSTANCE_PREFIX=%s' % cluster,
   612        # GCE
   613        'INSTANCE_PREFIX=%s' % cluster,
   614        'KUBE_GCE_INSTANCE_PREFIX=%s' % cluster,
   615      )
   616  
   617      if args and args.image_family and args.image_project:
   618          mode.use_latest_image(args.image_family, args.image_project)
   619  
   620      mode.start(runner_args)
   621  
   622  def create_parser():
   623      """Create argparser."""
   624      parser = argparse.ArgumentParser()
   625      parser.add_argument(
   626          '--mode', default='local', choices=['local', 'docker'])
   627      parser.add_argument(
   628          '--env-file', default=[], action="append",
   629          help='Job specific environment file')
   630      parser.add_argument(
   631          '--env', default=[], action="append",
   632          help='Job specific environment setting ' +
   633          '(usage: "--env=VAR=SETTING" will set VAR to SETTING).')
   634      parser.add_argument(
   635          '--image-family',
   636          help='The image family from which to fetch the latest image')
   637      parser.add_argument(
   638          '--image-project',
   639          help='The image project from which to fetch the test images')
   640      parser.add_argument(
   641          '--gce-ssh',
   642          default=os.environ.get('JENKINS_GCE_SSH_PRIVATE_KEY_FILE'),
   643          help='Path to .ssh/google_compute_engine keys')
   644      parser.add_argument(
   645          '--gce-pub',
   646          default=os.environ.get('JENKINS_GCE_SSH_PUBLIC_KEY_FILE'),
   647          help='Path to pub gce ssh key')
   648      parser.add_argument(
   649          '--service-account',
   650          default=os.environ.get('GOOGLE_APPLICATION_CREDENTIALS'),
   651          help='Path to service-account.json')
   652      parser.add_argument(
   653          '--mount-paths',
   654          action='append',
   655          help='Paths that should be mounted within the docker container in the form local:remote')
   656      parser.add_argument(
   657          '--build', nargs='?', default=None, const='',
   658          help='Build kubernetes binaries if set, optionally specifying strategy')
   659      parser.add_argument(
   660          '--use-shared-build', nargs='?', default=None, const='',
   661          help='Use prebuilt kubernetes binaries if set, optionally specifying strategy')
   662      parser.add_argument(
   663          '--gcs-shared',
   664          default='gs://kubernetes-jenkins/shared-results/',
   665          help='Get shared build from this bucket')
   666      parser.add_argument(
   667          '--cluster', default='bootstrap-e2e', help='Name of the cluster')
   668      parser.add_argument(
   669          '--docker-in-docker', action='store_true', help='Enable run docker within docker')
   670      parser.add_argument(
   671          '--kubeadm', choices=['ci', 'periodic', 'pull', 'stable'])
   672      parser.add_argument(
   673          '--stage', default=None, help='Stage release to GCS path provided')
   674      parser.add_argument(
   675          '--tag', default=DEFAULT_KUBEKINS_TAG, help='Use a specific kubekins-e2e tag if set')
   676      parser.add_argument(
   677          '--test', default='true', help='If we need to run any actual test within kubetest')
   678      parser.add_argument(
   679          '--down', default='true', help='If we need to tear down the e2e cluster')
   680      parser.add_argument(
   681          '--up', default='true', help='If we need to bring up a e2e cluster')
   682      parser.add_argument(
   683          '--use-logexporter',
   684          action='store_true',
   685          help='If we need to use logexporter tool to upload logs from nodes to GCS directly')
   686      parser.add_argument(
   687          '--kubetest_args',
   688          action='append',
   689          default=[],
   690          help='Send unrecognized args directly to kubetest')
   691  
   692      # aws
   693      parser.add_argument(
   694          '--aws', action='store_true', help='E2E job runs in aws')
   695      parser.add_argument(
   696          '--aws-profile',
   697          default=(
   698              os.environ.get('AWS_PROFILE') or
   699              os.environ.get('AWS_DEFAULT_PROFILE') or
   700              'default'
   701          ),
   702          help='Profile within --aws-cred to use')
   703      parser.add_argument(
   704          '--aws-role-arn',
   705          default=os.environ.get('KOPS_E2E_ROLE_ARN'),
   706          help='Use --aws-profile to run as --aws-role-arn if set')
   707      parser.add_argument(
   708          '--aws-ssh',
   709          default=os.environ.get('JENKINS_AWS_SSH_PRIVATE_KEY_FILE'),
   710          help='Path to private aws ssh keys')
   711      parser.add_argument(
   712          '--aws-pub',
   713          default=os.environ.get('JENKINS_AWS_SSH_PUBLIC_KEY_FILE'),
   714          help='Path to pub aws ssh key')
   715      parser.add_argument(
   716          '--aws-cred',
   717          default=os.environ.get('JENKINS_AWS_CREDENTIALS_FILE'),
   718          help='Path to aws credential file')
   719      parser.add_argument(
   720          '--aws-cluster-domain', help='Domain of the aws cluster for aws-pr jobs')
   721      parser.add_argument(
   722          '--kops-nodes', default=4, type=int, help='Number of nodes to start')
   723      parser.add_argument(
   724          '--kops-state', default='s3://k8s-kops-jenkins/',
   725          help='Name of the aws state storage')
   726      parser.add_argument(
   727          '--kops-zones', help='Comma-separated list of zones else random choice')
   728      parser.add_argument(
   729          '--kops-build', action='store_true', help='If we need to build kops locally')
   730  
   731      return parser
   732  
   733  
   734  def parse_args(args=None):
   735      """Return args, adding unrecognized args to kubetest_args."""
   736      parser = create_parser()
   737      args, extra = parser.parse_known_args(args)
   738      args.kubetest_args += extra
   739  
   740      if (args.image_family or args.image_project) and args.mode == 'docker':
   741          raise ValueError(
   742              '--image-family / --image-project is not supported in docker mode')
   743      if bool(args.image_family) != bool(args.image_project):
   744          raise ValueError(
   745              '--image-family and --image-project must be both set or unset')
   746  
   747      if args.aws:
   748          # If aws keys are missing, try to fetch from HOME dir
   749          if not args.aws_ssh or not args.aws_pub or not args.aws_cred:
   750              home = os.environ.get('HOME')
   751              if not home:
   752                  raise ValueError('HOME dir not set!')
   753              if not args.aws_ssh:
   754                  args.aws_ssh = '%s/.ssh/kube_aws_rsa' % home
   755                  print >>sys.stderr, '-aws-ssh key not set. Defaulting to %s' % args.aws_ssh
   756              if not args.aws_pub:
   757                  args.aws_pub = '%s/.ssh/kube_aws_rsa.pub' % home
   758                  print >>sys.stderr, '--aws-pub key not set. Defaulting to %s' % args.aws_pub
   759              if not args.aws_cred:
   760                  args.aws_cred = '%s/.aws/credentials' % home
   761                  print >>sys.stderr, '--aws-cred not set. Defaulting to %s' % args.aws_cred
   762      return args
   763  
   764  
   765  if __name__ == '__main__':
   766      main(parse_args())