Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Addition of a KubeLatentWorker #3155

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions common/code_spelling_ignore_words.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ amazonaws
andrew
apache
api
apimaster
app
appdata
apps
Expand All @@ -29,6 +30,7 @@ authz
autocommit
autodoc
autogenerated
awk
aws
axx
backend
Expand Down Expand Up @@ -385,6 +387,9 @@ keypair
keypairs
klass
knielsen
kube
kubernetes
kvm
kwarg
kwargs
lastlog
Expand Down Expand Up @@ -444,6 +449,7 @@ microsoft
middleware
minidom
minidom's
minikube
mispelling
misr
mit
Expand Down
1 change: 1 addition & 0 deletions master/buildbot/newsfragments/kube_latent_worker.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add a :py:class:`~buildbot.worker.kubernetes.KubeLatentWorker` to launch workers into a kubernetes cluster
147 changes: 147 additions & 0 deletions master/buildbot/test/fake/kubernetes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
# This file is part of Buildbot. Buildbot is free software: you can
# redistribute it and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation, version 2.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc., 51
# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Copyright Buildbot Team Members

from __future__ import absolute_import
from __future__ import print_function

import mock


class Client(object):

def __init__(self, configuration):
self.configuration = configuration

def _get_class(self, class_name):
return type(class_name, (_KubeObj,), {})

@property
def BatchV1Api(self):
return BatchV1Api

@property
def V1Job(self):
return self._get_class('V1Job')

@property
def V1ObjectMeta(self):
return self._get_class('V1ObjectMeta')

@property
def V1JobSpec(self):
return self._get_class('V1JobSpec')

@property
def V1PodTemplateSpec(self):
return self._get_class('V1PodTemplateSpec')

@property
def V1PodSpec(self):
return self._get_class('V1PodSpec')

@property
def V1Container(self):
return self._get_class('V1Container')

@property
def V1EnvVar(self):
return self._get_class('V1EnvVar')


class _KubeObj(mock.Mock):

def __init__(self, **kwargs):
super(_KubeObj, self).__init__()
self.kwargs = kwargs

@property
def swagger_types(self):
return self.kwargs

def __getattr__(self, key):
try:
return self.kwargs[key]
except KeyError:
raise AttributeError


class BatchV1Api(object):

def create_namespaced_job(self, namespace, job):
return job


class ConfigObj(object):

def __init__(self):
self.host = None

def _set_config(self, config):
for config_key, value in config.items():
setattr(self, config_key, value)

def __repr__(self):
return 'ConfigObj(%s)' % vars(self)


class ConfigException(object):

class ConfigException(Exception):
"""Mock of config_exception.ConfigException"""


class Config(object):

def __init__(self,
client_class=Client,
config_class=ConfigObj,
config_exception=ConfigException):
self._configuration_obj = config_class()
self._client_obj = client_class(self._configuration_obj)
self._config_exception = config_exception
self._kube_config_file = None
self._envvar_cluster = None

def _get_fake_client(self):
return self._client_obj

def _get_fake_config(self):
return self._configuration_obj

@property
def config_exception(self):
return self._config_exception

def load_kube_config(self):
if not self._kube_config_file:
raise IOError()
elif callable(self._kube_config_file):
raise self._kube_config_file()
else:
config = {
'host': self._kube_config_file.get('host', 'fake_host')
}
self._configuration_obj._set_config(config)

def load_incluster_config(self):
if not self._envvar_cluster:
raise self.config_exception.ConfigException
elif callable(self._envvar_cluster):
raise self._envvar_cluster()
else:
config = {
'host': self._envvar_cluster.get('host', 'fake_host')
}
self._configuration_obj._set_config(config)
125 changes: 125 additions & 0 deletions master/buildbot/test/integration/test_kubernetes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# This file is part of Buildbot. Buildbot is free software: you can
# redistribute it and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation, version 2.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc., 51
# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Copyright Buildbot Team Members

from __future__ import absolute_import
from __future__ import print_function

import os
from unittest.case import SkipTest

from twisted.internet import defer

from buildbot.config import BuilderConfig
from buildbot.plugins import schedulers
from buildbot.plugins import steps
from buildbot.process.factory import BuildFactory
from buildbot.process.results import SUCCESS
from buildbot.test.util.integration import RunMasterBase
from buildbot.worker import kubernetes as workerkube
from buildbot.worker.kubernetes import KubeLatentWorker

# This integration test creates a master and kubernetes worker environment,
# It requires a kubernetes cluster up and running. It tries to get the config
# like loading "~/.kube/config" files or environment variable.
# You can use minikube to create a kubernetes environment for development:

# # See https://github.com/kubernetes/minikube for full documentation
# minikube start # [--vm-driver=kvm]
# export masterFQDN=$(ip route get $(minikube ip)| awk '{ print $5 }')

# useful commands:
# - 'minikube dashboard' to display WebUI of the kubernetes cluster
# - 'minikube ip' to display the IP of the kube-apimaster
# - 'minikube ssh' to get a shell into the minikube VM

# following environment variable can be used to stress concurrent worker startup
NUM_CONCURRENT = int(os.environ.get("KUBE_TEST_NUM_CONCURRENT_BUILD", 1))


class KubernetesMaster(RunMasterBase):

def setUp(self):
if "TEST_KUBERNETES" not in os.environ:
raise SkipTest(
"kubernetes integration tests only run when environment "
"variable TEST_KUBERNETES is set")
if workerkube.client is None:
raise SkipTest("kubernetes (python client) is not installed")

@defer.inlineCallbacks
def test_trigger(self):
yield self.setupConfig(masterConfig(num_concurrent=NUM_CONCURRENT),
startWorker=False)
yield self.doForceBuild()

builds = yield self.master.data.get(("builds",))
# if there are some retry, there will be more builds
self.assertEqual(len(builds), 1 + NUM_CONCURRENT)
for b in builds:
self.assertEqual(b['results'], SUCCESS)


# master configuration
def masterConfig(num_concurrent, extra_steps=None):
if extra_steps is None:
extra_steps = []
c = {}

c['schedulers'] = [
schedulers.ForceScheduler(
name="force",
builderNames=["testy"])]
triggereables = []
for i in range(num_concurrent):
c['schedulers'].append(
schedulers.Triggerable(
name="trigsched" + str(i),
builderNames=["build"]))
triggereables.append("trigsched" + str(i))

f = BuildFactory()
f.addStep(steps.ShellCommand(command='echo hello'))
f.addStep(steps.Trigger(schedulerNames=triggereables,
waitForFinish=True,
updateSourceStamp=True))
f.addStep(steps.ShellCommand(command='echo world'))
f2 = BuildFactory()
f2.addStep(steps.ShellCommand(command='echo ola'))
for step in extra_steps:
f2.addStep(step)
c['builders'] = [
BuilderConfig(name="testy",
workernames=["kubernetes0"],
factory=f),
BuilderConfig(name="build",
workernames=["kubernetes" + str(i)
for i in range(num_concurrent)],
factory=f2)]
masterFQDN = os.environ.get('masterFQDN')
c['workers'] = [
KubeLatentWorker('kubernetes' + str(i), 'passwd',
masterFQDN=masterFQDN)
for i in range(num_concurrent)
]
# un comment for debugging what happens if things looks locked.
# c['www'] = {'port': 8080}
# if the masterFQDN is forced (proxy case), then we use 9989 default port
# else, we try to find a free port
if masterFQDN is not None:
c['protocols'] = {"pb": {"port": "tcp:9989"}}
else:
c['protocols'] = {"pb": {"port": "tcp:0"}}

return c