jenkins-bot has submitted this change and it was merged. Change subject: Add a webservice shell command ......................................................................
Add a webservice shell command - Only for kubernetes backend - Creates a pod, and attaches to bash inside that pod - Deletes pod when the child process exits Bug: T139952 Change-Id: Ie364fcefd06d6bdb929254c210f88bb86034d1ff --- M debian/changelog M scripts/webservice M toollabs/webservice/backends/kubernetesbackend.py 3 files changed, 128 insertions(+), 40 deletions(-) Approvals: BryanDavis: Looks good to me, approved jenkins-bot: Verified diff --git a/debian/changelog b/debian/changelog index aa13d4c..2e6cd54 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +toollabs-webservice (0.16) trusty; urgency=low + + * Add webservice shell command + + -- Yuvi Panda <[email protected]> Mon, 11 Jul 2016 20:50:00 +0100 + toollabs-webservice (0.15) trusty; urgency=low * Add python2 webservice support diff --git a/scripts/webservice b/scripts/webservice index 097ef93..9ec1e0e 100755 --- a/scripts/webservice +++ b/scripts/webservice @@ -16,7 +16,7 @@ argparser.add_argument('--backend', help='Which cluster backend to use run the webservice', choices=['gridengine', 'kubernetes']) argparser.add_argument('action', help='Action to perform', - choices=['start', 'stop', 'status', 'restart']) + choices=['start', 'stop', 'status', 'restart', 'shell']) argparser.add_argument('extra_args', help='Extra arguments to be parsed by the chosen webservicetype', nargs='?') # Backwards compat with older webservice. Allows for webservice --release precise lighttpd <action> @@ -143,4 +143,9 @@ print('Your webservice is running', end='') else: print('Your webservice is not running', end='') + elif args.action == 'shell': + if backend != 'kubernetes': + print('webservice shell only supported for kubernetes backend') + sys.exit() + job.shell() print() # End program with newline, don't output newline anywhere else :) diff --git a/toollabs/webservice/backends/kubernetesbackend.py b/toollabs/webservice/backends/kubernetesbackend.py index 25b7948..8dcbbbc 100644 --- a/toollabs/webservice/backends/kubernetesbackend.py +++ b/toollabs/webservice/backends/kubernetesbackend.py @@ -1,5 +1,8 @@ import os +import subprocess import pykube +import time +import sys from toollabs.webservice.backends import Backend from toollabs.webservice.services import LighttpdWebService, PythonWebService @@ -13,6 +16,7 @@ 'php5.6': { 'cls': LighttpdWebService, 'image': 'toollabs-php-web', + 'shell-image': 'toollabs-php-base', 'resources': { 'limits': { # Pods can't use more than these resource limits @@ -29,6 +33,7 @@ 'python2': { 'cls': PythonWebService, 'image': 'toollabs-python2-web', + 'shell-image': 'toollabs-python2-base', 'resources': { 'limits': { # Pods can't use more than these resource limits @@ -49,6 +54,9 @@ self.container_image = 'docker-registry.tools.wmflabs.org/{image}:latest'.format( image=KubernetesBackend.CONFIG[type]['image'] + ) + self.shell_image = 'docker-registry.tools.wmflabs.org/{image}:latest'.format( + image=KubernetesBackend.CONFIG[type]['shell-image'] ) self.container_resources = KubernetesBackend.CONFIG[type]['resources'] self.webservice = KubernetesBackend.CONFIG[type]['cls'](tool, extra_args) @@ -112,13 +120,6 @@ """ Return the full spec of the deployment object for this webservice """ - # All the paths we want to mount from host nodes onto container - hostMounts = { - 'home': '/data/project/', - 'scratch': '/data/scratch/', - 'dumps': '/public/dumps/' - } - cmd = [ "/usr/bin/webservice-runner", "--type", @@ -127,10 +128,14 @@ "8000" ] - homedir = '/data/project/{toolname}/'.format(toolname=self.tool.name) - if self.extra_args: cmd += " --extra-args '%s'" % self.extra_args + + ports = [{ + "name": "http", + "containerPort": 8000, + "protocol": "TCP" + }] return { "kind": "Deployment", @@ -149,38 +154,50 @@ "metadata": { "labels": self.labels }, - "spec": { - "volumes": [ - {"name": key, "hostPath": {"path": value}} - for key, value in hostMounts.items() - ], - "containers": [ - { - "name": "webservice", - "image": self.container_image, - "command": cmd, - "workingDir": homedir, - "env": [ - # FIXME: This should be set by NSS maybe?! - {"name": "HOME", "value": homedir} - ], - "ports": [ - { - "name": "http", - "containerPort": 8000, - "protocol": "TCP" - } - ], - "volumeMounts": [ - {"name": key, "mountPath": value} - for key, value in hostMounts.items() - ], - "resources": self.container_resources - } - ], - } + "spec": self._get_container_spec( + "webservice", + self.container_image, + cmd, + self.container_resources, + ports + ) } } + } + + def _get_container_spec(self, name, container_image, cmd, resources, ports=None, stdin=False, tty=False): + # All the paths we want to mount from host nodes onto container + hostMounts = { + 'home': '/data/project/', + 'scratch': '/data/scratch/', + 'dumps': '/public/dumps/' + } + + homedir = '/data/project/{toolname}/'.format(toolname=self.tool.name) + + return { + "volumes": [ + {"name": key, "hostPath": {"path": value}} + for key, value in hostMounts.items() + ], + "containers": [{ + "name": name, + "image": container_image, + "command": cmd, + "workingDir": homedir, + "env": [ + # FIXME: This should be set by NSS maybe?! + {"name": "HOME", "value": homedir} + ], + "ports": ports, + "volumeMounts": [ + {"name": key, "mountPath": value} + for key, value in hostMounts.items() + ], + "resources": resources, + "tty": tty, + "stdin": stdin + }], } def request_start(self): @@ -217,3 +234,63 @@ # FIXME: Check if pod is running as well return Backend.STATE_RUNNING return Backend.STATE_STOPPED + + def shell(self): + labels = { + 'tools.wmflabs.org/webservice-interactive': 'true', + 'tools.wmflabs.org/webservice-interactive-version': '1', + 'name': 'interactive' + } + label_selector = ','.join( + ['{k}={v}'.format(k=k, v=v) for k, v in labels.items()] + ) + + podSpec = { + 'apiVersion': 'v1', + 'kind': 'Pod', + 'metadata': { + 'namespace': self.tool.name, + 'name': 'interactive', + 'labels': labels, + }, + 'spec': self._get_container_spec( + 'interactive', + self.shell_image, + ['/bin/bash', '-i', '-l'], + resources=None, + ports=None, + stdin=True, + tty=True + ) + } + if self._find_obj(pykube.Pod, label_selector) is None: + pykube.Pod(self.api, podSpec).create() + + # Wait 30s for pod to become ready + count = 0 + while count < 30: + pod = self._find_obj(pykube.Pod, label_selector) + if pod is not None: + if pod.obj['status']['phase'] == 'Running': + break + count += 1 + time.sleep(1) + else: + print("Pod creation failed, please report this as a bug!") + sys.exit() + kubectl = subprocess.Popen([ + '/usr/local/bin/kubectl', + 'attach', + '--tty', + '--stdin', + 'interactive' + ]) + + kubectl.wait() + # kubectl attach prints the following when done: + # Session ended, resume using 'kubectl attach interactive -c interactive -i -t' command when the pod is running + # This isn't true, since we actually kill the pod when done + print("Pod stopped. Session cannot be resumed.") + + pod = self._find_obj(pykube.Pod, label_selector) + pykube.Pod(self.api, pod.obj).delete() -- To view, visit https://gerrit.wikimedia.org/r/298318 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: merged Gerrit-Change-Id: Ie364fcefd06d6bdb929254c210f88bb86034d1ff Gerrit-PatchSet: 10 Gerrit-Project: operations/software/tools-webservice Gerrit-Branch: master Gerrit-Owner: Yuvipanda <[email protected]> Gerrit-Reviewer: BryanDavis <[email protected]> Gerrit-Reviewer: jenkins-bot <> _______________________________________________ MediaWiki-commits mailing list [email protected] https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits
