Skip to content

Commit 585cdaf

Browse files
authored
Merge pull request #2001 from fejta/prow
Add a --mode=local flag to kubernetes_e2e scenario
2 parents 390b26e + 6e828fa commit 585cdaf

File tree

3 files changed

+194
-101
lines changed

3 files changed

+194
-101
lines changed

jenkins/bootstrap.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -810,8 +810,7 @@ def bootstrap(
810810

811811
def parse_args(arguments=None):
812812
"""Parse arguments or sys.argv[1:]."""
813-
parser = argparse.ArgumentParser(
814-
'Checks out a github PR/branch to <basedir>/<repo>/')
813+
parser = argparse.ArgumentParser()
815814
parser.add_argument(
816815
'--json',
817816
nargs='?', const=1, default=0,

jenkins/e2e-image/e2e-runner.sh

+3-29
Original file line numberDiff line numberDiff line change
@@ -356,11 +356,6 @@ fi
356356
# $ sudo chgrp -R jenkins /var/lib/jenkins/gce_keys/
357357
case "${KUBERNETES_PROVIDER}" in
358358
gce|gke|kubemark)
359-
if ! running_in_docker; then
360-
mkdir -p ${WORKSPACE}/.ssh/
361-
cp /var/lib/jenkins/gce_keys/google_compute_engine ${WORKSPACE}/.ssh/
362-
cp /var/lib/jenkins/gce_keys/google_compute_engine.pub ${WORKSPACE}/.ssh/
363-
fi
364359
echo 'Checking existence of private ssh key'
365360
gce_key="${WORKSPACE}/.ssh/google_compute_engine"
366361
if [[ ! -f "${gce_key}" || ! -f "${gce_key}.pub" ]]; then
@@ -414,30 +409,9 @@ fi
414409

415410
cd kubernetes
416411

417-
if [[ -n "${BOOTSTRAP_MIGRATION:-}" ]]; then
418-
# TODO(fejta): migrate all jobs and stop using upload-to-gcs.sh to do this
419-
# Right now started.json is created by e2e-runner.sh and
420-
# finished.json is created by jenkins.
421-
# Soon we will consolodate this responsibility inside bootstrap.py
422-
# We want to switch to this logic as we start migrating jobs over to this
423-
# pattern, but until then we also need jobs to continue uploading started.json
424-
# until that time. This environment variable will do that.
425-
source "$(dirname "${0}")/upload-to-gcs.sh"
426-
version=$(find_version) # required by print_started
427-
print_started | jq '.metadata? + {version, "job-version"}' > "${ARTIFACTS}/metadata.json"
428-
elif [[ ! "${JOB_NAME}" =~ -pull- ]]; then
429-
echo "The bootstrapper should handle Tracking the start/finish of a job and "
430-
echo "uploading artifacts. TODO(fejta): migrate this job"
431-
# Upload build start time and k8s version to GCS, but not on PR Jenkins.
432-
# On PR Jenkins this is done before the build.
433-
upload_to_gcs="$(dirname "${0}")/upload-to-gcs.sh"
434-
if [[ -f "${upload_to_gcs}" ]]; then
435-
JENKINS_BUILD_STARTED=true "${upload_to_gcs}"
436-
else
437-
echo "TODO(fejta): stop pulling upload-to-gcs.sh"
438-
JENKINS_BUILD_STARTED=true bash <(curl -fsS --retry 3 --keepalive-time 2 "https://raw.githubusercontent.com/kubernetes/kubernetes/master/hack/jenkins/upload-to-gcs.sh")
439-
fi
440-
fi
412+
source "$(dirname "${0}")/upload-to-gcs.sh"
413+
version=$(find_version) # required by print_started
414+
print_started | jq '.metadata? + {version, "job-version"}' > "${ARTIFACTS}/metadata.json"
441415

442416
if [[ -n "${PRIORITY_PATH:-}" ]]; then
443417
export PATH="${PRIORITY_PATH}:${PATH}"

scenarios/kubernetes_e2e.py

+190-70
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import argparse
2323
import os
24+
import shutil
2425
import signal
2526
import subprocess
2627
import sys
@@ -44,49 +45,169 @@ def check(*cmd):
4445
subprocess.check_call(cmd)
4546

4647

47-
def sig_handler(_signo, _frame):
48-
"""Stops container upon receive signal.SIGTERM and signal.SIGINT."""
49-
print >>sys.stderr, 'signo = %s, frame = %s' % (_signo, _frame)
50-
check('docker', 'stop', CONTAINER)
48+
def check_env(env, *cmd):
49+
"""Log and run the command with a specific env, raising on errors."""
50+
print >>sys.stderr, 'Environment:'
51+
for key, value in env.items():
52+
print >>sys.stderr, '%s=%s' % (key, value)
53+
print >>sys.stderr, 'Run:', cmd
54+
subprocess.check_call(cmd, env=env)
5155

5256

5357
def kubekins(tag):
5458
"""Return full path to kubekins-e2e:tag."""
5559
return 'gcr.io/k8s-testimages/kubekins-e2e:%s' % tag
5660

5761

58-
def main(args):
59-
"""Set up env, start kubekins-e2e, handle termination. """
60-
# pylint: disable=too-many-locals
61-
62-
# dockerized-e2e-runner goodies setup
63-
workspace = os.environ.get('WORKSPACE', os.getcwd())
64-
artifacts = '%s/_artifacts' % workspace
65-
if not os.path.isdir(artifacts):
66-
os.makedirs(artifacts)
62+
class LocalMode(object):
63+
"""Runs e2e tests by calling e2e-runner.sh."""
64+
def __init__(self, workspace):
65+
self.workspace = workspace
66+
self.env = []
67+
self.env_files = []
68+
self.add_environment(
69+
'HOME=%s' % workspace,
70+
'WORKSPACE=%s' % workspace,
71+
'PATH=%s' % os.getenv('PATH'),
72+
)
73+
74+
@staticmethod
75+
def parse_env(env):
76+
"""Returns (FOO, BAR=MORE) for FOO=BAR=MORE."""
77+
return env.split('=', 1)
78+
79+
def add_environment(self, *envs):
80+
"""Adds FOO=BAR to the list of environment overrides."""
81+
self.env.extend(self.parse_env(e) for e in envs)
82+
83+
def add_files(self, env_files):
84+
"""Reads all FOO=BAR lines from each path in env_files seq."""
85+
for env_file in env_files:
86+
with open(test_infra(env_file)) as fp:
87+
for line in fp:
88+
line = line.rstrip()
89+
if not line or line.startswith('#'):
90+
continue
91+
self.env_files.append(self.parse_env(line))
92+
93+
def add_gce_ssh(self, priv, pub):
94+
"""Copies priv, pub keys to $WORKSPACE/.ssh."""
95+
gce_ssh = '%s/.ssh/google_compute_engine' % self.workspace
96+
gce_pub = '%s/.ssh/google_compute_engine.pub' % self.workspace
97+
shutil.copy(priv, gce_ssh)
98+
shutil.copy(pub, gce_pub)
99+
self.add_environment(
100+
'JENKINS_GCE_SSH_PRIVATE_KEY_FILE=%s' % gce_ssh,
101+
'JENKINS_GCE_SSH_PUBLIC_KEY_FILE=%s' % gce_pub,
102+
)
103+
104+
def add_service_account(self, path):
105+
"""Sets GOOGLE_APPLICATION_CREDENTIALS to path."""
106+
self.add_environment('GOOGLE_APPLICATION_CREDENTIALS=%s' % path)
107+
108+
@property
109+
def runner(self):
110+
"""Finds the best version of e2e-runner.sh."""
111+
options = ['e2e-runner.sh', test_infra('jenkins/e2e-image/e2e-runner.sh')]
112+
for path in options:
113+
if os.path.isfile(path):
114+
return path
115+
raise ValueError('Cannot find e2e-runner at any of %s' % ', '.join(options))
116+
117+
118+
def install_prerequisites(self):
119+
"""Copies upload-to-gcs and kubetest if needed."""
120+
parent = os.path.dirname(self.runner)
121+
if not os.path.isfile(os.path.join(parent, 'upload-to-gcs.sh')):
122+
shutil.copy(
123+
test_infra('../kubernetes/hack/jenkins/upload-to-gcs.sh'),
124+
os.path.join(parent, 'upload-to-gcs.sh'))
125+
if not os.path.isfile(os.path.join(parent, 'kubetest')):
126+
check('go', 'install', 'k8s.io/test-infra/kubetest')
127+
shutil.copy(
128+
os.path.expandvars('${GOPATH}/bin/kubetest'),
129+
os.path.join(parent, 'kubetest'))
130+
131+
def start(self):
132+
"""Runs e2e-runner.sh after setting env and installing prereqs."""
133+
env = {}
134+
env.update(self.env_files)
135+
env.update(self.env)
136+
self.install_prerequisites()
137+
check_env(env, self.runner)
138+
139+
140+
class DockerMode(object):
141+
"""Runs e2e tests via docker run kubekins-e2e."""
142+
def __init__(self, container, workspace, sudo, tag, mount_paths):
143+
self.tag = tag
144+
try: # Pull a newer version if one exists
145+
check('docker', 'pull', kubekins(tag))
146+
except subprocess.CalledProcessError:
147+
pass
148+
149+
print 'Starting %s...' % container
150+
151+
self.container = container
152+
self.cmd = [
153+
'docker', 'run', '--rm',
154+
'--name=%s' % container,
155+
'-v', '%s/_artifacts:/workspace/_artifacts' % workspace,
156+
'-v', '/etc/localtime:/etc/localtime:ro',
157+
]
158+
for path in mount_paths:
159+
self.cmd.extend(['-v', path])
160+
161+
if sudo:
162+
self.cmd.extend(['-v', '/var/run/docker.sock:/var/run/docker.sock'])
163+
self.add_environment(
164+
'HOME=/workspace',
165+
'WORKSPACE=/workspace')
166+
167+
def add_environment(self, *envs):
168+
"""Adds FOO=BAR to the -e list for docker."""
169+
for env in envs:
170+
self.cmd.extend(['-e', env])
171+
172+
def add_files(self, env_files):
173+
"""Adds each file to the --env-file list."""
174+
for env_file in env_files:
175+
self.cmd.extend(['--env-file', test_infra(env_file)])
176+
177+
178+
def add_gce_ssh(self, priv, pub):
179+
"""Mounts priv and pub inside the container."""
180+
gce_ssh = '/workspace/.ssh/google_compute_engine'
181+
gce_pub = '%s.pub' % gce_ssh
182+
self.cmd.extend([
183+
'-v', '%s:%s:ro' % (priv, gce_ssh),
184+
'-v', '%s:%s:ro' % (pub, gce_pub),
185+
'-e', 'JENKINS_GCE_SSH_PRIVATE_KEY_FILE=%s' % gce_ssh,
186+
'-e', 'JENKINS_GCE_SSH_PUBLIC_KEY_FILE=%s' % gce_pub])
67187

68-
try: # Pull a newer version if one exists
69-
check('docker', 'pull', kubekins(args.tag))
70-
except subprocess.CalledProcessError:
71-
pass
188+
def add_service_account(self, path):
189+
"""Mounts GOOGLE_APPLICATION_CREDENTIALS inside the container."""
190+
service = '/service-account.json'
191+
self.cmd.extend([
192+
'-v', '%s:%s:ro' % (path, service),
193+
'-e', 'GOOGLE_APPLICATION_CREDENTIALS=%s' % service])
72194

73-
print 'Starting %s...' % CONTAINER
74195

75-
cmd = [
76-
'docker', 'run', '--rm',
77-
'--name=%s' % CONTAINER,
78-
'-v', '%s/_artifacts:/workspace/_artifacts' % workspace,
79-
'-v', '/etc/localtime:/etc/localtime:ro'
80-
]
196+
def start(self):
197+
"""Runs kubekins."""
198+
self.cmd.append(kubekins(self.tag))
199+
signal.signal(signal.SIGTERM, self.sig_handler)
200+
signal.signal(signal.SIGINT, self.sig_handler)
201+
check(*self.cmd)
81202

82-
if args.docker_in_docker:
83-
cmd.extend([
84-
'-v', '/var/run/docker.sock:/var/run/docker.sock'])
203+
def sig_handler(self, _signo, _frame):
204+
"""Stops container upon receive signal.SIGTERM and signal.SIGINT."""
205+
print >>sys.stderr, 'docker stop (signo=%s, frame=%s)' % (_signo, _frame)
206+
check('docker', 'stop', self.container)
85207

86-
if args.mount_paths:
87-
for path in args.mount_paths:
88-
cmd.extend(['-v', path])
89208

209+
def main(args):
210+
"""Set up env, start kubekins-e2e, handle termination. """
90211
# Rules for env var priority here in docker:
91212
# -e FOO=a -e FOO=b -> FOO=b
92213
# --env-file FOO=a --env-file FOO=b -> FOO=b
@@ -96,48 +217,49 @@ def main(args):
96217
# So if you overwrite FOO=c for a local run it will take precedence.
97218
#
98219

220+
# dockerized-e2e-runner goodies setup
221+
workspace = os.environ.get('WORKSPACE', os.getcwd())
222+
artifacts = '%s/_artifacts' % workspace
223+
if not os.path.isdir(artifacts):
224+
os.makedirs(artifacts)
225+
226+
container = '%s-%s' % (os.environ.get('JOB_NAME'), os.environ.get('BUILD_NUMBER'))
227+
if args.mode == 'docker':
228+
mode = DockerMode(container, workspace, args.docker_in_docker, args.tag, args.mount_paths)
229+
elif args.mode == 'local':
230+
mode = LocalMode(workspace) # pylint: disable=redefined-variable-type
231+
else:
232+
raise ValueError(args.mode)
99233
if args.env_file:
100-
for env in args.env_file:
101-
cmd.extend(['--env-file', test_infra(env)])
234+
mode.add_files(args.env_file)
102235

103236
if args.gce_ssh:
104-
gce_ssh = '/workspace/.ssh/google_compute_engine'
105-
gce_pub = '%s.pub' % gce_ssh
106-
cmd.extend([
107-
'-v', '%s:%s:ro' % (args.gce_ssh, gce_ssh),
108-
'-v', '%s:%s:ro' % (args.gce_pub, gce_pub),
109-
'-e', 'JENKINS_GCE_SSH_PRIVATE_KEY_FILE=%s' % gce_ssh,
110-
'-e', 'JENKINS_GCE_SSH_PUBLIC_KEY_FILE=%s' % gce_pub])
237+
mode.add_gce_ssh(args.gce_ssh, args.gce_pub)
111238

112239
if args.service_account:
113-
service = '/service-account.json'
114-
cmd.extend([
115-
'-v', '%s:%s:ro' % (args.service_account, service),
116-
'-e', 'GOOGLE_APPLICATION_CREDENTIALS=%s' % service])
240+
mode.add_service_account(args.service_account)
117241

118-
cmd.extend([
242+
mode.add_environment(
119243
# Boilerplate envs
120244
# Skip gcloud update checking
121-
'-e', 'CLOUDSDK_COMPONENT_MANAGER_DISABLE_UPDATE_CHECK=true',
245+
'CLOUDSDK_COMPONENT_MANAGER_DISABLE_UPDATE_CHECK=true',
122246
# Use default component update behavior
123-
'-e', 'CLOUDSDK_EXPERIMENTAL_FAST_COMPONENT_UPDATE=false',
247+
'CLOUDSDK_EXPERIMENTAL_FAST_COMPONENT_UPDATE=false',
124248
# E2E
125-
'-e', 'E2E_UP=%s' % args.up,
126-
'-e', 'E2E_TEST=%s' % args.test,
127-
'-e', 'E2E_DOWN=%s' % args.down,
128-
'-e', 'E2E_NAME=%s' % args.cluster,
249+
'E2E_UP=%s' % args.up,
250+
'E2E_TEST=%s' % args.test,
251+
'E2E_DOWN=%s' % args.down,
252+
'E2E_NAME=%s' % args.cluster,
129253
# AWS
130-
'-e', 'KUBE_AWS_INSTANCE_PREFIX=%s' % args.cluster,
254+
'KUBE_AWS_INSTANCE_PREFIX=%s' % args.cluster,
131255
# GCE
132-
'-e', 'INSTANCE_PREFIX=%s' % args.cluster,
133-
'-e', 'KUBE_GCE_NETWORK=%s' % args.cluster,
134-
'-e', 'KUBE_GCE_INSTANCE_PREFIX=%s' % args.cluster,
256+
'INSTANCE_PREFIX=%s' % args.cluster,
257+
'KUBE_GCE_NETWORK=%s' % args.cluster,
258+
'KUBE_GCE_INSTANCE_PREFIX=%s' % args.cluster,
135259
# GKE
136-
'-e', 'CLUSTER_NAME=%s' % args.cluster,
137-
'-e', 'KUBE_GKE_NETWORK=%s' % args.cluster,
138-
# Workspace
139-
'-e', 'HOME=/workspace',
140-
'-e', 'WORKSPACE=/workspace'])
260+
'CLUSTER_NAME=%s' % args.cluster,
261+
'KUBE_GKE_NETWORK=%s' % args.cluster,
262+
)
141263

142264
# env blacklist.
143265
# TODO(krzyzacy) change this to a whitelist
@@ -150,25 +272,25 @@ def main(args):
150272
'WORKSPACE'
151273
]
152274

153-
for key, value in os.environ.items():
154-
if key not in docker_env_ignore:
155-
cmd.extend(['-e', '%s=%s' % (key, value)])
275+
# TODO(fejta): delete this
276+
mode.add_environment(*(
277+
'%s=%s' % (k, v) for (k, v) in os.environ.items()
278+
if k not in docker_env_ignore))
156279

157280
# Overwrite JOB_NAME for soak-*-test jobs
158281
if args.soak_test and os.environ.get('JOB_NAME'):
159-
cmd.extend(['-e', 'JOB_NAME=%s' % os.environ.get('JOB_NAME').replace('-test', '-deploy')])
282+
mode.add_environment('JOB_NAME=%s' % os.environ.get('JOB_NAME').replace('-test', '-deploy'))
160283

161-
cmd.append(kubekins(args.tag))
284+
mode.start()
162285

163-
signal.signal(signal.SIGTERM, sig_handler)
164-
signal.signal(signal.SIGINT, sig_handler)
165-
166-
check(*cmd)
167286

168287

169288
if __name__ == '__main__':
170289

171290
PARSER = argparse.ArgumentParser()
291+
292+
PARSER.add_argument(
293+
'--mode', default='docker', choices=['local', 'docker'])
172294
PARSER.add_argument(
173295
'--env-file', action="append", help='Job specific environment file')
174296

@@ -207,6 +329,4 @@ def main(args):
207329
'--up', default='true', help='If we need to set --up in e2e.go')
208330
ARGS = PARSER.parse_args()
209331

210-
CONTAINER = '%s-%s' % (os.environ.get('JOB_NAME'), os.environ.get('BUILD_NUMBER'))
211-
212332
main(ARGS)

0 commit comments

Comments
 (0)