diff --git a/.travis.yml b/.travis.yml index 02f2bae6e..7fec53925 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,16 +1,18 @@ # ref: https://docs.travis-ci.com/user/languages/python language: python -python: - - "2.7" - - "3.4" - - "3.5" -# command to install dependencies +sudo: true +services: + - docker +env: + - TOXENV=py35,codecov + - TOXENV=py34,codecov + - TOXENV=py27,codecov + - TOXENV=py27-functional,codecov + - TOXENV=py35-functional,codecov + - TOXENV=coverage,codecov + install: - - "pip install -r requirements.txt" - - "pip install codecov" + - pip install tox -# command to run tests -script: nosetests --with-coverage --cover-package=kubernetes.config,kubernetes.watch --cover-tests - -after_success: - - bash <(curl -s https://codecov.io/bash) +script: + - tox diff --git a/kubernetes/e2e_test/__init__.py b/kubernetes/e2e_test/__init__.py new file mode 100644 index 000000000..19f5e722f --- /dev/null +++ b/kubernetes/e2e_test/__init__.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- + +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. diff --git a/kubernetes/e2e_test/test_k8sclient.py b/kubernetes/e2e_test/test_k8sclient.py new file mode 100644 index 000000000..e4b8d0162 --- /dev/null +++ b/kubernetes/e2e_test/test_k8sclient.py @@ -0,0 +1,204 @@ +# -*- coding: utf-8 -*- + +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +test_k8sclient +---------------------------------- + +Tests for `k8sclient` module. Deploy Kubernetes using: +http://kubernetes.io/docs/getting-started-guides/docker/ + +and then run this test +""" + +import unittest +import urllib3 +import uuid + +from kubernetes.client import api_client +from kubernetes.client.apis import core_v1_api + + +def _is_k8s_running(): + try: + urllib3.PoolManager().request('GET', '127.0.0.1:8080') + return True + except urllib3.exceptions.HTTPError: + return False + + +class TestK8sclient(unittest.TestCase): + @unittest.skipUnless( + _is_k8s_running(), "Kubernetes is not available") + def test_list_endpoints(self): + client = api_client.ApiClient('http://127.0.0.1:8080/') + api = core_v1_api.CoreV1Api(client) + + endpoints = api.list_endpoints_for_all_namespaces() + self.assertTrue(len(endpoints.items) > 0) + + @unittest.skipUnless( + _is_k8s_running(), "Kubernetes is not available") + def test_pod_apis(self): + client = api_client.ApiClient('http://127.0.0.1:8080/') + api = core_v1_api.CoreV1Api(client) + + name = 'test-' + str(uuid.uuid4()) + + pod_manifest = {'apiVersion': 'v1', + 'kind': 'Pod', + 'metadata': {'color': 'blue', 'name': name}, + 'spec': {'containers': [{'image': 'dockerfile/redis', + 'name': 'redis'}]}} + + resp = api.create_namespaced_pod(body=pod_manifest, + namespace='default') + self.assertEqual(name, resp.metadata.name) + self.assertTrue(resp.status.phase) + + resp = api.read_namespaced_pod(name=name, + namespace='default') + self.assertEqual(name, resp.metadata.name) + self.assertTrue(resp.status.phase) + + number_of_pods = len(api.list_pod_for_all_namespaces().items) + self.assertTrue(number_of_pods > 0) + + resp = api.delete_namespaced_pod(name=name, body={}, + namespace='default') + + @unittest.skipUnless( + _is_k8s_running(), "Kubernetes is not available") + def test_service_apis(self): + client = api_client.ApiClient('http://127.0.0.1:8080/') + api = core_v1_api.CoreV1Api(client) + + service_manifest = {'apiVersion': 'v1', + 'kind': 'Service', + 'metadata': {'labels': {'name': 'frontend'}, + 'name': 'frontend', + 'resourceversion': 'v1'}, + 'spec': {'ports': [{'name': 'port', + 'port': 80, + 'protocol': 'TCP', + 'targetPort': 80}], + 'selector': {'name': 'frontend'}}} + + resp = api.create_namespaced_service(body=service_manifest, + namespace='default') + self.assertEqual('frontend', resp.metadata.name) + self.assertTrue(resp.status) + + resp = api.read_namespaced_service(name='frontend', + namespace='default') + self.assertEqual('frontend', resp.metadata.name) + self.assertTrue(resp.status) + + # TODO(dims) : Fails with "json: cannot unmarshal object into + # Go value of type jsonpatch.Patch" + # service_manifest['spec']['ports'] = [{'name': 'new', + # 'port': 8080, + # 'protocol': 'TCP', + # 'targetPort': 8080}] + # resp = api.patch_namespaced_service(body=service_manifest, + # name='frontend', + # namespace='default') + # self.assertEqual(2, len(resp.spec.ports)) + # self.assertTrue(resp.status) + + resp = api.delete_namespaced_service(name='frontend', + namespace='default') + + @unittest.skipUnless( + _is_k8s_running(), "Kubernetes is not available") + def test_replication_controller_apis(self): + client = api_client.ApiClient('http://127.0.0.1:8080/') + api = core_v1_api.CoreV1Api(client) + + rc_manifest = { + 'apiVersion': 'v1', + 'kind': 'ReplicationController', + 'metadata': {'labels': {'name': 'frontend'}, + 'name': 'frontend'}, + 'spec': {'replicas': 2, + 'selector': {'name': 'frontend'}, + 'template': {'metadata': { + 'labels': {'name': 'frontend'}}, + 'spec': {'containers': [{ + 'image': 'nginx', + 'name': 'nginx', + 'ports': [{'containerPort': 80, + 'protocol': 'TCP'}]}]}}}} + + resp = api.create_namespaced_replication_controller( + body=rc_manifest, namespace='default') + self.assertEqual('frontend', resp.metadata.name) + self.assertEqual(2, resp.spec.replicas) + + resp = api.read_namespaced_replication_controller( + name='frontend', namespace='default') + self.assertEqual('frontend', resp.metadata.name) + self.assertEqual(2, resp.spec.replicas) + + resp = api.delete_namespaced_replication_controller( + name='frontend', body={}, namespace='default') + + + @unittest.skipUnless( + _is_k8s_running(), "Kubernetes is not available") + def test_configmap_apis(self): + client = api_client.ApiClient('http://127.0.0.1:8080/') + api = core_v1_api.CoreV1Api(client) + + test_configmap = { + "kind": "ConfigMap", + "apiVersion": "v1", + "metadata": { + "name": "test-configmap", + }, + "data": { + "config.json": "{\"command\":\"/usr/bin/mysqld_safe\"}", + "frontend.cnf": "[mysqld]\nbind-address = 10.0.0.3\nport = 3306\n" + } + } + + resp = api.create_namespaced_config_map( + body=test_configmap, namespace='default' + ) + self.assertEqual('test-configmap', resp.metadata.name) + + resp = api.read_namespaced_config_map( + name='test-configmap', namespace='default') + self.assertEqual('test-configmap', resp.metadata.name) + + # TODO(dims): Fails with "json: cannot unmarshal object + # into Go value of type jsonpatch.Patch" + # test_configmap['data']['config.json'] = "{}" + # resp = api.patch_namespaced_config_map( + # name='test-configmap', namespace='default', body=test_configmap) + + resp = api.delete_namespaced_config_map( + name='test-configmap', body={}, namespace='default') + + + @unittest.skipUnless( + _is_k8s_running(), "Kubernetes is not available") + def test_node_apis(self): + client = api_client.ApiClient('http://127.0.0.1:8080/') + api = core_v1_api.CoreV1Api(client) + + for item in api.list_node().items: + node = api.read_node(name=item.metadata.name) + self.assertTrue(len(node.metadata.labels) > 0) + self.assertTrue(isinstance(node.metadata.labels, dict)) \ No newline at end of file diff --git a/scripts/kube-init.sh b/scripts/kube-init.sh new file mode 100755 index 000000000..1348fa02b --- /dev/null +++ b/scripts/kube-init.sh @@ -0,0 +1,126 @@ +#!/bin/bash + +# Copyright 2017 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -x + +function clean_exit(){ + local error_code="$?" + local spawned=$(jobs -p) + if [ -n "$spawned" ]; then + kill $(jobs -p) + fi + return $error_code +} + +trap "clean_exit" EXIT + +# Switch off SE-Linux +setenforce 0 + +# Install docker if needed +path_to_executable=$(which docker) +if [ -x "$path_to_executable" ] ; then + echo "Found Docker installation" +else + curl -sSL https://get.docker.io | sudo bash +fi +docker --version + +# Get the latest stable version of kubernetes +export K8S_VERSION=$(curl -sS https://storage.googleapis.com/kubernetes-release/release/stable.txt) +echo "K8S_VERSION : ${K8S_VERSION}" + +echo "Starting docker service" +sudo systemctl enable docker.service +sudo systemctl start docker.service --ignore-dependencies +echo "Checking docker service" +sudo docker ps + +# Run the docker containers for kubernetes +echo "Starting Kubernetes containers" +sudo docker run \ + --volume=/:/rootfs:ro \ + --volume=/sys:/sys:ro \ + --volume=/var/lib/docker/:/var/lib/docker:rw \ + --volume=/var/lib/kubelet/:/var/lib/kubelet:rw \ + --volume=/var/run:/var/run:rw \ + --net=host \ + --pid=host \ + --privileged=true \ + --name=kubelet \ + -d \ + gcr.io/google_containers/hyperkube-amd64:${K8S_VERSION} \ + /hyperkube kubelet \ + --containerized \ + --hostname-override="127.0.0.1" \ + --address="0.0.0.0" \ + --api-servers=http://localhost:8080 \ + --config=/etc/kubernetes/manifests \ + --allow-privileged=true --v=2 + + +echo "Download Kubernetes CLI" +wget -O kubectl "http://storage.googleapis.com/kubernetes-release/release/${K8S_VERSION}/bin/linux/amd64/kubectl" +chmod 755 kubectl +./kubectl get nodes + +set +x +echo "Waiting for master components to start..." +for i in {1..300} +do + running_count=$(./kubectl -s=http://127.0.0.1:8080 get pods --no-headers 2>/dev/null | grep "Running" | wc -l) + # We expect to have 3 running pods - etcd, master and kube-proxy. + if [ "$running_count" -ge 3 ]; then + break + fi + echo -n "." + sleep 1 +done +set -x + +echo "SUCCESS" +echo "Cluster created!" +echo "" + +echo "Dump Kubernetes Objects..." +./kubectl -s=http://127.0.0.1:8080 get componentstatuses +./kubectl -s=http://127.0.0.1:8080 get configmaps +./kubectl -s=http://127.0.0.1:8080 get daemonsets +./kubectl -s=http://127.0.0.1:8080 get deployments +./kubectl -s=http://127.0.0.1:8080 get events +./kubectl -s=http://127.0.0.1:8080 get endpoints +./kubectl -s=http://127.0.0.1:8080 get horizontalpodautoscalers +./kubectl -s=http://127.0.0.1:8080 get ingress +./kubectl -s=http://127.0.0.1:8080 get jobs +./kubectl -s=http://127.0.0.1:8080 get limitranges +./kubectl -s=http://127.0.0.1:8080 get nodes +./kubectl -s=http://127.0.0.1:8080 get namespaces +./kubectl -s=http://127.0.0.1:8080 get pods +./kubectl -s=http://127.0.0.1:8080 get persistentvolumes +./kubectl -s=http://127.0.0.1:8080 get persistentvolumeclaims +./kubectl -s=http://127.0.0.1:8080 get quota +./kubectl -s=http://127.0.0.1:8080 get resourcequotas +./kubectl -s=http://127.0.0.1:8080 get replicasets +./kubectl -s=http://127.0.0.1:8080 get replicationcontrollers +./kubectl -s=http://127.0.0.1:8080 get secrets +./kubectl -s=http://127.0.0.1:8080 get serviceaccounts +./kubectl -s=http://127.0.0.1:8080 get services + + +echo "Running tests..." +set -x -e +# Yield execution to venv command +$* \ No newline at end of file diff --git a/test-requirements.txt b/test-requirements.txt index 629bcbd88..9aca512a0 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -5,4 +5,5 @@ py>=1.4.31 randomize>=0.13 mock>=2.0.0 sphinx>=1.2.1,!=1.3b1,<1.4 # BSD -recommonmark \ No newline at end of file +recommonmark +codecov>=1.4.0 \ No newline at end of file diff --git a/tox.ini b/tox.ini index be6a14ca5..1378e20b4 100644 --- a/tox.ini +++ b/tox.ini @@ -5,9 +5,29 @@ envlist = py27, py34, py35 commands = python setup.py build_sphinx [testenv] -deps=-r{toxinidir}/requirements.txt - -r{toxinidir}/test-requirements.txt - -commands= - nosetests \ - [] +passenv = TOXENV CI TRAVIS TRAVIS_* +usedevelop = True +install_command = pip install -U {opts} {packages} +deps = -r{toxinidir}/test-requirements.txt +commands = + python -V + nosetests [] + +[testenv:py27-functional] +commands = + python -V + {toxinidir}/scripts/kube-init.sh nosetests [] + +[testenv:py35-functional] +commands = + python -V + {toxinidir}/scripts/kube-init.sh nosetests [] + +[testenv:coverage] +commands = + python -V + nosetests --with-coverage --cover-package=kubernetes.config,kubernetes.watch --cover-tests + +[testenv:codecov] +commands = + codecov