Skip to content

Commit

Permalink
Merge e3d0a1b into 2e89211
Browse files Browse the repository at this point in the history
  • Loading branch information
NiallEgan committed Aug 9, 2018
2 parents 2e89211 + e3d0a1b commit 06c9880
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 210 deletions.
225 changes: 94 additions & 131 deletions aimmo-game-creator/game_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@
import os
import subprocess
import time
import kubernetes
from abc import ABCMeta, abstractmethod

import pykube
import requests
from eventlet.greenpool import GreenPool
from eventlet.semaphore import Semaphore
import kubernetes


LOGGER = logging.getLogger(__name__)

K8S_NAMESPACE = 'default'


class _GameManagerData(object):
"""This class is thread safe"""
Expand Down Expand Up @@ -166,9 +168,10 @@ class KubernetesGameManager(GameManager):
"""Manages games running on Kubernetes cluster"""

def __init__(self, *args, **kwargs):
self._api = pykube.HTTPClient(pykube.KubeConfig.from_service_account())
kubernetes.config.load_incluster_config()
self._api_instance = kubernetes.client.ExtensionsV1beta1Api()
self.extension_api = kubernetes.client.ExtensionsV1beta1Api()
self.api = kubernetes.client.CoreV1Api()

super(KubernetesGameManager, self).__init__(*args, **kwargs)
self._create_ingress_paths_for_existing_games()

Expand All @@ -178,121 +181,72 @@ def _create_ingress_paths_for_existing_games(self):
self._add_path_to_ingress(game_id)

@staticmethod
def _create_game_name(id):
def _create_game_name(game_id):
"""
Creates a name that will be used as the pod name as well as in other places.
:param id: Integer indicating the GAME_ID of the game.
:param game_id: Integer indicating the GAME_ID of the game.
:return: A string with the game appended with the id.
"""
return "game-{}".format(id)

def _create_game_rc(self, id, environment_variables):
environment_variables["SOCKETIO_RESOURCE"] = KubernetesGameManager._create_game_name(id)
environment_variables["GAME_ID"] = id
environment_variables["GAME_URL"] = "http://game-{}".format(id)
environment_variables["PYKUBE_KUBERNETES_SERVICE_HOST"] = "kubernetes"
environment_variables["IMAGE_SUFFIX"] = os.environ.get("IMAGE_SUFFIX", "latest")
rc = pykube.ReplicationController(
self._api,
{
"kind": "ReplicationController",
"apiVersion": "v1",
"metadata": {
"name": KubernetesGameManager._create_game_name(id),
"namespace": "default",
"labels": {
"app": "aimmo-game",
"game_id": id,
},
},
"spec": {
"replicas": 1,
"selector": {
"app": "aimmo-game",
"game_id": id,
},
"template": {
"metadata": {
"labels": {
"app": "aimmo-game",
"game_id": id,
},
},
"spec": {
"containers": [
{
"env": [
{
"name": env_name,
"value": env_value,
} for env_name, env_value in environment_variables.items()
],
"image": "ocadotechnology/aimmo-game:{}".format(os.environ.get("IMAGE_SUFFIX", "latest")),
"ports": [
{
"containerPort": 5000,
},
],
"name": "aimmo-game",
"resources": {
"limits": {
"cpu": "300m",
"memory": "128Mi",
},
"requests": {
"cpu": "10m",
"memory": "64Mi",
},
},
"securityContext": {
"capabilities": {
"drop": [
"all"
],
"add": [
"NET_BIND_SERVICE"
]
}
}
},
],
},
},
},
},
)
rc.create()

def _create_game_service(self, id, _config):
service = pykube.Service(
self._api,
{
"kind": "Service",
"apiVersion": "v1",
"metadata": {
"name": KubernetesGameManager._create_game_name(id),
"labels": {
"app": "aimmo-game",
"game_id": id,
},
},
"spec": {
"selector": {
"app": "aimmo-game",
"game_id": id,
},
"ports": [
{
"protocol": "TCP",
"port": 80,
"targetPort": 5000,
},
],
"type": "NodePort",
},
},
)
service.create()
return "game-{}".format(game_id)

def _make_rc(self, environment_variables, game_id):
container = kubernetes.client.V1Container(
env=[kubernetes.client.V1EnvVar(
name=env_name,
value=env_value) for env_name, env_value in environment_variables.items()],
image='ocadotechnology/aimmo-game:{}'.format(os.environ.get('IMAGE_SUFFIX', 'latest')),
ports=[kubernetes.client.V1ContainerPort(container_port=5000)],
name='aimmo-game',
resources=kubernetes.client.V1ResourceRequirements(
limits={'cpu': '300m', 'memory': '128Mi'},
requests={'cpu': '10m', 'memory': '64Mi'}),
security_context=kubernetes.client.V1SecurityContext(
capabilities=kubernetes.client.V1Capabilities(
drop=['all'],
add=['NET_BIND_SERVICE'])))

pod_manifest = kubernetes.client.V1PodSpec(containers=[container])
pod_metadata = kubernetes.client.V1ObjectMeta(labels={'app': 'aimmo-game', 'game_id': game_id})
pod_template_manifest = kubernetes.client.V1PodTemplateSpec(spec=pod_manifest, metadata=pod_metadata)

rc_manifest = kubernetes.client.V1ReplicationControllerSpec(
template=pod_template_manifest,
selector={'app': 'aimmo-game',
'game_id': game_id},
replicas=1)

rc_metadata = kubernetes.client.V1ObjectMeta(
name=KubernetesGameManager._create_game_name(game_id),
namespace=K8S_NAMESPACE,
labels={'app': 'aimmo-game', 'game_id': game_id})

return kubernetes.client.V1ReplicationController(spec=rc_manifest, metadata=rc_metadata)

def _create_game_rc(self, game_id, environment_variables):
environment_variables['SOCKETIO_RESOURCE'] = KubernetesGameManager._create_game_name(game_id)
environment_variables['GAME_ID'] = game_id
environment_variables['GAME_URL'] = 'http://game-{}'.format(game_id)
environment_variables['IMAGE_SUFFIX'] = os.environ.get('IMAGE_SUFFIX', 'latest')
environment_variables['K8S_NAMESPACE'] = K8S_NAMESPACE

rc = self._make_rc(environment_variables, game_id)
self.api.create_namespaced_replication_controller(K8S_NAMESPACE, rc)

def _make_service(self, game_id):
service_manifest = kubernetes.client.V1ServiceSpec(
selector={'app': 'aimmo-game', 'game_id': game_id},
ports=[kubernetes.client.V1ServicePort(protocol='TCP', port=80, target_port=5000)],
type='NodePort')

service_metadata = kubernetes.client.V1ObjectMeta(
name=KubernetesGameManager._create_game_name(game_id),
labels={'app': 'aimmo-game', 'game_id': game_id})

return kubernetes.client.V1Service(metadata=service_metadata, spec=service_manifest)

def _create_game_service(self, game_id):
service = self._make_service(game_id)
self.api.create_namespaced_service(K8S_NAMESPACE, service)

def _add_path_to_ingress(self, game_id):
backend = kubernetes.client.V1beta1IngressBackend(KubernetesGameManager._create_game_name(game_id), 80)
Expand All @@ -307,13 +261,13 @@ def _add_path_to_ingress(self, game_id):
}
]

self._api_instance.patch_namespaced_ingress("aimmo-ingress", "default", patch)
self.extension_api.patch_namespaced_ingress("aimmo-ingress", "default", patch)

def _remove_path_from_ingress(self, game_id):
backend = kubernetes.client.V1beta1IngressBackend(KubernetesGameManager._create_game_name(game_id), 80)
path = kubernetes.client.V1beta1HTTPIngressPath(backend,
"/{}".format(KubernetesGameManager._create_game_name(game_id)))
ingress = self._api_instance.list_namespaced_ingress("default").items[0]
ingress = self.extension_api.list_namespaced_ingress("default").items[0]
paths = ingress.spec.rules[0].http.paths
try:
index_to_delete = paths.index(path)
Expand All @@ -327,28 +281,37 @@ def _remove_path_from_ingress(self, game_id):
}
]

self._api_instance.patch_namespaced_ingress("aimmo-ingress", "default", patch)
self.extension_api.patch_namespaced_ingress("aimmo-ingress", "default", patch)

def _remove_resources(self, game_id, resource_type):
resource_functions = {'Pod': (self.api.list_namespaced_pod, self.api.delete_namespaced_pod),
'ReplicationController': (self.api.list_namespaced_replication_controller,
self.api.delete_namespaced_replication_controller),
'Service': (self.api.list_namespaced_service, self.api.delete_namespaced_service)}

list_resource_function, delete_resource_function = resource_functions[resource_type]

app_label = 'app=aimmo-game'
game_label = 'game_id={}'.format(game_id)

resources = list_resource_function(namespace=K8S_NAMESPACE,
label_selector=','.join([app_label, game_label]))

for resource in resources.items:
LOGGER.info('Removing: {}'.format(resource.metadata.name))
delete_resource_function(resource.metadata.name, K8S_NAMESPACE, kubernetes.client.V1DeleteOptions())

def create_game(self, game_id, game_data):
try:
self._create_game_service(game_id, game_data)
except pykube.exceptions.HTTPError as err:
if "already exists" in err.message:
LOGGER.warning("Service for game {} already existed".format(game_id))
else:
raise
self._create_game_service(game_id)
self._create_game_rc(game_id, game_data)
self._add_path_to_ingress(game_id)
LOGGER.info("Game started - {}".format(game_id))

def delete_game(self, game_id):
self._remove_path_from_ingress(game_id)
for object_type in (pykube.ReplicationController, pykube.Service, pykube.Pod):
for game in object_type.objects(self._api).\
filter(selector={"app": "aimmo-game",
"game_id": game_id}):
LOGGER.info("Removing {}: {}".format(object_type.__name__, game.name))
game.delete()
self._remove_resources(game_id, 'ReplicationController')
self._remove_resources(game_id, 'Pod')
self._remove_resources(game_id, 'Service')


GAME_MANAGERS = {
Expand Down
1 change: 0 additions & 1 deletion aimmo-game-creator/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
pykube
eventlet
kubernetes == 5.0.0
1 change: 0 additions & 1 deletion aimmo-game-creator/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
include_package_data=True,
install_requires=[
'eventlet',
'pykube',
'kubernetes == 5.0.0'
],
tests_require=[
Expand Down
2 changes: 1 addition & 1 deletion aimmo-game/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
eventlet
flask
python-socketio==2.0.0
pykube
requests
six
flask-cors
kubernetes
2 changes: 1 addition & 1 deletion aimmo-game/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
'python-socketio==2.0.0',
'requests',
'six',
'pykube',
'kubernetes'
],
tests_require=[
'httmock',
Expand Down
Loading

0 comments on commit 06c9880

Please sign in to comment.