From 7a48f954fee72e30c31bcedf759f6e9ceb5d53cc Mon Sep 17 00:00:00 2001 From: Olaf Szmidt Date: Fri, 6 Jul 2018 11:19:19 +0100 Subject: [PATCH] Revert Vagrant #624 changes. Fixes #649 (#650) * Revert Vagrant #624 changes. Fixes #649 This reverts commit b957777f5b5a6d82fe10bbbd6a295afc2c1489bf. Revert "Remove Vagrant option from runner (#644)" This reverts commit 91b31bcc66490833014d9182d274ce0bd4dc6e93. Further minor changes. Fixes #649 * Clean up revert changes. Adds pipfile back in and removes vagrantfile Pip docs back to normal Delete Vagrantfile Copy the old Pipfile.lock in More cleanup after reverting * add kubectl to docs * refactor restart_pods to be <26 lines :muscle: * python <3 whitespace * newline * Minor comment change to be more clear --- .gitignore | 6 - aimmo_runner/kubernetes_setup.py | 130 ---------- aimmo_runner/minikube.py | 256 ++++++++++++++------ aimmo_runner/runner.py | 37 ++- aimmo_runner/tests/test_minikube_runner.py | 6 +- docs/usage.md | 2 + example_project/example_project/settings.py | 2 + kubeconfig_aimmo.yml | 17 -- run.py | 2 +- 9 files changed, 205 insertions(+), 253 deletions(-) delete mode 100644 aimmo_runner/kubernetes_setup.py delete mode 100644 kubeconfig_aimmo.yml diff --git a/.gitignore b/.gitignore index 331b86754..980fbab9c 100644 --- a/.gitignore +++ b/.gitignore @@ -86,9 +86,3 @@ players/templates/players/game_ide.html # Game Frontend node_modules - -# Vagrant -.vagrant/** - -# VSCode -game_frontend/\.vscode/ diff --git a/aimmo_runner/kubernetes_setup.py b/aimmo_runner/kubernetes_setup.py deleted file mode 100644 index 62db2560c..000000000 --- a/aimmo_runner/kubernetes_setup.py +++ /dev/null @@ -1,130 +0,0 @@ -#!/user/bin/env python -from __future__ import print_function - -import docker -import kubernetes -import os -import re -import socket -import yaml -import platform -from shell_api import (run_command, create_test_bin, BASE_DIR) -from abc import ABCMeta, abstractmethod - - -class KubernetesBaseSetup(): - __metaclass__ = ABCMeta - - def get_ip(self): - """ - Get a single primary IP address. This will not return all IPs in the - interface. See http://stackoverflow.com/a/28950776/671626 - :return: Integer with the IP of the user. - """ - os_name = platform.system() - if os_name == "Darwin": - return socket.gethostbyname(socket.gethostname()) - - client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - - # noinspection PyBroadException - try: - # doesn't even have to be reachable - client_socket.connect(('10.255.255.255', 0)) - IP = client_socket.getsockname()[0] - except: - IP = '127.0.0.1' - finally: - client_socket.close() - return IP - - def create_ingress_yaml(self): - """ - Loads a ingress yaml file into a python object. - """ - path = os.path.join(BASE_DIR, 'ingress.yaml') - with open(path) as yaml_file: - content = yaml.safe_load(yaml_file.read()) - return content - - def create_creator_yaml(self): - """ - Loads a replication controller yaml file into a python object. - """ - orig_path = os.path.join(BASE_DIR, 'aimmo-game-creator', 'rc-aimmo-game-creator.yaml') - with open(orig_path) as orig_file: - content = yaml.safe_load(orig_file.read().replace('latest', 'test').replace('REPLACE_ME', 'http://%s:8000/players/api/games/' % self.get_ip())) - return content - - @abstractmethod - def create_docker_client(self): - pass - - def build_docker_images(self): - """ - Finds environment settings and builds docker images for each directory. - :param minikube: Executable command to run in terminal. - """ - print('Building docker images') - - client = self.create_docker_client() - - directories = ('aimmo-game', 'aimmo-game-creator', 'aimmo-game-worker') - for dir in directories: - path = os.path.join(BASE_DIR, dir) - tag = 'ocadotechnology/%s:test' % dir - print("Building %s..." % tag) - client.images.build( - path=path, - tag=tag, - encoding='gzip' - ) - - @abstractmethod - def load_kube_config(self): - pass - - def restart_pods(self, game_creator_yaml, ingress_yaml): - """ - Disables all the components running in the cluster and starts them again - with fresh updated state. - :param game_creator_yaml: Replication controller yaml settings file. - :param ingress_yaml: Ingress yaml settings file. - """ - print('Restarting pods') - self.load_kube_config() - api_instance = kubernetes.client.CoreV1Api() - extensions_api_instance = kubernetes.client.ExtensionsV1beta1Api() - for rc in api_instance.list_namespaced_replication_controller('default').items: - api_instance.delete_namespaced_replication_controller( - body=kubernetes.client.V1DeleteOptions(), - name=rc.metadata.name, - namespace='default') - for pod in api_instance.list_namespaced_pod('default').items: - api_instance.delete_namespaced_pod( - body=kubernetes.client.V1DeleteOptions(), - name=pod.metadata.name, - namespace='default') - for service in api_instance.list_namespaced_service('default').items: - api_instance.delete_namespaced_service( - name=service.metadata.name, - namespace='default') - for ingress in extensions_api_instance.list_namespaced_ingress('default').items: - extensions_api_instance.delete_namespaced_ingress( - name=ingress.metadata.name, - namespace='default', - body=kubernetes.client.V1DeleteOptions()) - - extensions_api_instance.create_namespaced_ingress("default", ingress_yaml) - api_instance.create_namespaced_replication_controller( - body=game_creator_yaml, - namespace='default', - ) - - @abstractmethod - def start(): - """ - The entry point to the kubernetes setup. Sends calls appropriately to set - up kubernetes. - """ - pass diff --git a/aimmo_runner/minikube.py b/aimmo_runner/minikube.py index 13a14f6e6..fa783f235 100644 --- a/aimmo_runner/minikube.py +++ b/aimmo_runner/minikube.py @@ -6,82 +6,190 @@ import os import re import platform +import yaml +import socket from shell_api import (run_command, create_test_bin, BASE_DIR) -from aimmo_runner.kubernetes_setup import KubernetesBaseSetup MINIKUBE_EXECUTABLE = "minikube" -class MinikubeRunner(KubernetesBaseSetup): - - def restart_ingress_addon(self): - """ - Ingress needs to be restarted for old paths to be removed at startup. - :param minikube: Executable minikube installed beforehand. - """ - try: - run_command([MINIKUBE_EXECUTABLE, 'addons', 'disable', 'ingress']) - except: - pass - run_command([MINIKUBE_EXECUTABLE, 'addons', 'enable', 'ingress']) - - def start_cluster(self): - """ - Starts the cluster unless it has been already started by the user. - :param minikube: Executable minikube installed beforehand. - """ - status = run_command([MINIKUBE_EXECUTABLE, 'status'], True) - if 'minikube: Running' in status: - print('Cluster already running') - else: - run_command([MINIKUBE_EXECUTABLE, 'start', '--memory=2048', '--cpus=2']) - - def create_docker_client(self): - """ - Creates a docker client using the python SDK. - :param raw_env_settings: String that is returned by the 'minikube docker-env' command. - :return: - """ - raw_env_settings = run_command([MINIKUBE_EXECUTABLE, 'docker-env', '--shell="bash"'], True) - if self.vm_none_enabled(raw_env_settings): - matches = re.finditer(r'^export (.+)="(.+)"$', raw_env_settings, re.MULTILINE) - env_variables = dict([(m.group(1), m.group(2)) for m in matches]) - - return docker.from_env( - environment=env_variables, - version='auto', - ) - else: - # VM driver is set - return docker.from_env( - version='auto' - ) - - def vm_none_enabled(self, raw_env_settings): - """ - Check if the VM driver is enabled or not. This is important to see where - the environment variables live. - :param raw_env_settings: String that is returned by the 'minikube docker-env' command. - :return: Boolean value indicating if enabled or not. - """ - return False if 'driver does not support' in raw_env_settings else True - - def load_kube_config(self): - kubernetes.config.load_kube_config(context='minikube') - - def start(self): - """ - The entry point to the minikube class. Sends calls appropriately to set - up minikube. - """ - if platform.machine().lower() not in ('amd64', 'x86_64'): - raise ValueError('Requires 64-bit') - create_test_bin() - os.environ['MINIKUBE_PATH'] = MINIKUBE_EXECUTABLE - self.start_cluster() - self.build_docker_images() - self.restart_ingress_addon() - ingress = self.create_ingress_yaml() - game_creator = self.create_creator_yaml() - self.restart_pods(game_creator, ingress) - print('Cluster ready') +def get_ip(): + """ + Get a single primary IP address. This will not return all IPs in the + interface. See http://stackoverflow.com/a/28950776/671626 + :return: Integer with the IP of the user. + """ + os_name = platform.system() + if os_name == "Darwin": + return socket.gethostbyname(socket.gethostname()) + + client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + + # noinspection PyBroadException + try: + # doesn't even have to be reachable + client_socket.connect(('10.255.255.255', 0)) + IP = client_socket.getsockname()[0] + except: + IP = '127.0.0.1' + finally: + client_socket.close() + return IP + + +def restart_ingress_addon(minikube): + """ + Ingress needs to be restarted for old paths to be removed at startup. + :param minikube: Executable minikube installed beforehand. + """ + try: + run_command([minikube, 'addons', 'disable', 'ingress']) + except: + pass + run_command([minikube, 'addons', 'enable', 'ingress']) + + +def create_ingress_yaml(): + """ + Loads a ingress yaml file into a python object. + """ + path = os.path.join(BASE_DIR, 'ingress.yaml') + with open(path) as yaml_file: + content = yaml.safe_load(yaml_file.read()) + return content + + +def create_creator_yaml(): + """ + Loads a replication controller yaml file into a python object. + """ + orig_path = os.path.join(BASE_DIR, 'aimmo-game-creator', 'rc-aimmo-game-creator.yaml') + with open(orig_path) as orig_file: + content = yaml.safe_load(orig_file.read().replace('latest', 'test').replace('REPLACE_ME', 'http://%s:8000/players/api/games/' % get_ip())) + return content + + +def start_cluster(minikube): + """ + Starts the cluster unless it has been already started by the user. + :param minikube: Executable minikube installed beforehand. + """ + status = run_command([minikube, 'status'], True) + if 'minikube: Running' in status: + print('Cluster already running') + else: + run_command([minikube, 'start', '--memory=2048', '--cpus=2']) + + +def create_docker_client(raw_env_settings): + """ + Creates a docker client using the python SDK. + :param raw_env_settings: String that is returned by the 'minikube docker-env' command. + :return: + """ + if vm_none_enabled(raw_env_settings): + matches = re.finditer(r'^export (.+)="(.+)"$', raw_env_settings, re.MULTILINE) + env_variables = dict([(m.group(1), m.group(2)) for m in matches]) + + return docker.from_env( + environment=env_variables, + version='auto', + ) + else: + # VM driver is set + return docker.from_env( + version='auto' + ) + + +def vm_none_enabled(raw_env_settings): + """ + Check if the VM driver is enabled or not. This is important to see where + the environment variables live. + :param raw_env_settings: String that is returned by the 'minikube docker-env' command. + :return: Boolean value indicating if enabled or not. + """ + return False if 'driver does not support' in raw_env_settings else True + + +def build_docker_images(minikube): + """ + Finds environment settings and builds docker images for each directory. + :param minikube: Executable command to run in terminal. + """ + print('Building docker images') + raw_env_settings = run_command([minikube, 'docker-env', '--shell="bash"'], True) + + client = create_docker_client(raw_env_settings) + + directories = ('aimmo-game', 'aimmo-game-creator', 'aimmo-game-worker') + for dir in directories: + path = os.path.join(BASE_DIR, dir) + tag = 'ocadotechnology/%s:test' % dir + print("Building %s..." % tag) + client.images.build( + path=path, + tag=tag, + encoding='gzip' + ) + + +def delete_components(api_instance, extensions_api_instance): + for rc in api_instance.list_namespaced_replication_controller('default').items: + api_instance.delete_namespaced_replication_controller( + body=kubernetes.client.V1DeleteOptions(), + name=rc.metadata.name, + namespace='default') + for pod in api_instance.list_namespaced_pod('default').items: + api_instance.delete_namespaced_pod( + body=kubernetes.client.V1DeleteOptions(), + name=pod.metadata.name, + namespace='default') + for service in api_instance.list_namespaced_service('default').items: + api_instance.delete_namespaced_service( + name=service.metadata.name, + namespace='default') + for ingress in extensions_api_instance.list_namespaced_ingress('default').items: + extensions_api_instance.delete_namespaced_ingress( + name=ingress.metadata.name, + namespace='default', + body=kubernetes.client.V1DeleteOptions()) + + +def restart_pods(game_creator_yaml, ingress_yaml): + """ + Disables all the components running in the cluster and starts them again + with fresh updated state. + :param game_creator_yaml: Replication controller yaml settings file. + :param ingress_yaml: Ingress yaml settings file. + """ + print('Restarting pods') + kubernetes.config.load_kube_config(context='minikube') + api_instance = kubernetes.client.CoreV1Api() + extensions_api_instance = kubernetes.client.ExtensionsV1beta1Api() + + delete_components(api_instance, extensions_api_instance) + + extensions_api_instance.create_namespaced_ingress("default", ingress_yaml) + api_instance.create_namespaced_replication_controller( + body=game_creator_yaml, + namespace='default', + ) + + +def start(): + """ + The entry point to the minikube class. Sends calls appropriately to set + up minikube. + """ + if platform.machine().lower() not in ('amd64', 'x86_64'): + raise ValueError('Requires 64-bit') + create_test_bin() + os.environ['MINIKUBE_PATH'] = MINIKUBE_EXECUTABLE + start_cluster(MINIKUBE_EXECUTABLE) + build_docker_images(MINIKUBE_EXECUTABLE) + restart_ingress_addon(MINIKUBE_EXECUTABLE) + ingress = create_ingress_yaml() + game_creator = create_creator_yaml() + restart_pods(game_creator, ingress) + print('Cluster ready') diff --git a/aimmo_runner/runner.py b/aimmo_runner/runner.py index 47709899b..eb8663e9e 100644 --- a/aimmo_runner/runner.py +++ b/aimmo_runner/runner.py @@ -33,24 +33,6 @@ def create_superuser_if_missing(username, password): password=password) -def install_kubernetes_dependencies(capture_output): - parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - sys.path.append(os.path.join(parent_dir, 'aimmo_runner')) - - os.chdir(ROOT_DIR_LOCATION) - run_command(['pip', 'install', '-r', os.path.join(ROOT_DIR_LOCATION, 'minikube_requirements.txt')], - capture_output=capture_output) - - os.environ['AIMMO_MODE'] = 'minikube' - - -def setup_minikube(capture_output): - install_kubernetes_dependencies(capture_output) - # Import minikube here, so we can install the deps first - from aimmo_runner.minikube import MinikubeRunner - MinikubeRunner().start() - - def run(use_minikube, server_wait=True, capture_output=False, test_env=False): logging.basicConfig() if test_env: @@ -64,11 +46,23 @@ def run(use_minikube, server_wait=True, capture_output=False, test_env=False): run_command(['python', _MANAGE_PY, 'migrate', '--noinput'], capture_output=capture_output) run_command(['python', _MANAGE_PY, 'collectstatic', '--noinput'], capture_output=capture_output) - django.setup() create_superuser_if_missing(username='admin', password='admin') + server_args = [] if use_minikube: - setup_minikube(capture_output) + parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + sys.path.append(os.path.join(parent_dir, 'aimmo_runner')) + + os.chdir(ROOT_DIR_LOCATION) + run_command(['pip', 'install', '-r', os.path.join(ROOT_DIR_LOCATION, 'minikube_requirements.txt')], + capture_output=capture_output) + + # Import minikube here, so we can install the dependencies first + from aimmo_runner import minikube + minikube.start() + + server_args.append('0.0.0.0:8000') + os.environ['AIMMO_MODE'] = 'minikube' else: time.sleep(2) game = run_command_async(['python', _SERVICE_PY, '127.0.0.1', '5000'], capture_output=capture_output) @@ -76,8 +70,7 @@ def run(use_minikube, server_wait=True, capture_output=False, test_env=False): os.environ['AIMMO_MODE'] = 'threads' os.environ['NODE_ENV'] = 'development' if settings.DEBUG else 'production' - - server = run_command_async(['python', _MANAGE_PY, 'runserver'], capture_output=capture_output) + server = run_command_async(['python', _MANAGE_PY, 'runserver'] + server_args, capture_output=capture_output) frontend_bundler = run_command_async(['node', _FRONTEND_BUNDLER_JS], capture_output=capture_output) PROCESSES.append(server) PROCESSES.append(frontend_bundler) diff --git a/aimmo_runner/tests/test_minikube_runner.py b/aimmo_runner/tests/test_minikube_runner.py index 671c3ee1c..47c506ab8 100644 --- a/aimmo_runner/tests/test_minikube_runner.py +++ b/aimmo_runner/tests/test_minikube_runner.py @@ -1,19 +1,19 @@ import logging import mock from unittest import TestCase -from aimmo_runner.minikube import MinikubeRunner +from aimmo_runner.minikube import create_creator_yaml logging.basicConfig(level=logging.WARNING) class TestMinikubeRunner(TestCase): - @mock.patch('aimmo_runner.minikube.MinikubeRunner.get_ip', return_value='127.0.0.1') + @mock.patch('aimmo_runner.minikube.get_ip', return_value='127.0.0.1') def test_game_creator_function_creates_correct_game_url(self, mocked_get_ip_func): """ Checks if the game creator yaml is created with the correct game API URL. Relies on get_ip() which we expect to fall back to localhost always. """ - created_yaml = MinikubeRunner().create_creator_yaml() + created_yaml = create_creator_yaml() self.assertEqual(created_yaml['spec']['template']['spec']['containers'][0]['env'][1]['value'], 'http://127.0.0.1:8000/players/api/games/') diff --git a/docs/usage.md b/docs/usage.md index 3e0808aa8..3b95bbb99 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -41,6 +41,8 @@ * Install a fixed minikube version (at the time of this article this is 0.25.2 but you can confirm that [here](https://github.com/ocadotechnology/aimmo/blob/b0fd1bf852b1b2630a8546d173798ec9a670c480/.travis.yml#L23)). To do this write `curl -Lo minikube https://storage.googleapis.com/minikube/releases/v0.25.2/minikube-darwin-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/ ` and replace the version with the desired one. + * Install kubectl: + `curl -Lo kubectl https://storage.googleapis.com/kubernetes-release/release/v1.9.4/bin/darwin/amd64/kubectl && chmod +x kubectl && sudo mv kubectl /usr/local/bin/ * On Ubuntu ([download snap](https://snapcraft.io/)) and run `sudo snap install kubectl --classic` then follow the ([docker installation instructions](https://docs.docker.com/install/linux/docker-ce/ubuntu/)). * On Windows ([download chocolatey](https://chocolatey.org/)) and run `choco install kubernetes-cli` followed by the ([docker installation instructions for Windows](https://docs.docker.com/docker-for-windows/)). * Alter your `/etc/hosts` file by adding the following to the end of the file: `192.168.99.100 local.aimmo.codeforlife.education`. You may be required to run this with `sudo` as the file is protected. diff --git a/example_project/example_project/settings.py b/example_project/example_project/settings.py index 5b7309907..b002646c6 100644 --- a/example_project/example_project/settings.py +++ b/example_project/example_project/settings.py @@ -111,6 +111,8 @@ def get_game_url_base_and_path(game): if os.environ.get('AIMMO_MODE', '') == 'minikube': + output = subprocess.check_output([os.environ['MINIKUBE_PATH'], 'service', + 'game-%s' % game, '--url']) return 'local.aimmo.codeforlife.education', '/game-%s' % game else: return 'localhost', '' diff --git a/kubeconfig_aimmo.yml b/kubeconfig_aimmo.yml deleted file mode 100644 index c7dd7f646..000000000 --- a/kubeconfig_aimmo.yml +++ /dev/null @@ -1,17 +0,0 @@ -apiVersion: v1 -kind: Config -users: -- name: myself - user: - username: myself - password: secret -clusters: -- name: local - cluster: - server: http://localhost:8080 -contexts: -- context: - cluster: local - user: myself - name: service-account-context -current-context: service-account-context diff --git a/run.py b/run.py index 8548c99ca..fae699132 100755 --- a/run.py +++ b/run.py @@ -8,7 +8,7 @@ if __name__ == '__main__': try: - runner.run(use_minikube='--kube' in sys.argv or '-k' in sys.argv) + runner.run('--kube' in sys.argv or '-k' in sys.argv) except Exception as err: traceback.print_exc() raise