diff --git a/Makefile b/Makefile index bd4f223f..06fed434 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,15 @@ test: pip install -qr test-requirements.txt $(PYTHON) -m pytest tests/units/ -vv --cov atomicapp +.PHONY: functional-test +functional-test: + pip install -qr requirements.txt + pip install -qr test-requirements.txt + ./tests/functional/scripts/atomic.sh install + ./tests/functional/scripts/prepare.sh install + $(DOCKER) build -t atomicapp:build . + $(PYTHON) -m pytest tests/functional/ -vv --cov atomicapp + .PHONY: image image: $(DOCKER) build -t $(tag) . diff --git a/atomicapp/constants.py b/atomicapp/constants.py index 4139f93f..b37cc5b0 100644 --- a/atomicapp/constants.py +++ b/atomicapp/constants.py @@ -43,6 +43,8 @@ NAMESPACE_SEPARATOR = ":" REQUIREMENTS_KEY = "requirements" +K8S_VERSION = '1.3.5' + # Nulecule spec terminology vs the function within /providers REQUIREMENT_FUNCTIONS = { "persistentVolume": "persistent_storage" diff --git a/tests/functional/README.md b/tests/functional/README.md new file mode 100644 index 00000000..3c88df11 --- /dev/null +++ b/tests/functional/README.md @@ -0,0 +1,14 @@ +## System tests for atomicapp + +### Usage +From root directory of ``atomicapp`` repo, do: + +``` +sudo NULECULE_LIB= py.test tests/system +``` + +To test individual provider, you can do the following: + +``` +sudo NULECULE_LIB= py.test tests/system/test__provider.py +``` diff --git a/tests/functional/__init__.py b/tests/functional/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/functional/base.py b/tests/functional/base.py new file mode 100644 index 00000000..37cb8b9c --- /dev/null +++ b/tests/functional/base.py @@ -0,0 +1,615 @@ +import distutils.dir_util +import os +import re +import logging +import logging.config +import time +import anymarkup +import datetime +import unittest +import subprocess +from collections import OrderedDict +import tempfile +import uuid + +from .providers import kubernetes +from .providers import openshift + +LOGGING_CONF = dict( + version=1, + formatters=dict( + bare={ + "datefmt": "%Y-%m-%d %H:%M:%S", + "format": "[%(asctime)s][%(name)10s %(levelname)7s] %(message)s" + }, + ), + handlers=dict( + console={ + "class": "logging.StreamHandler", + "formatter": "bare", + "level": "DEBUG", + "stream": "ext://sys.stderr", + } + ), + loggers=dict( + test={ + "level": "DEBUG", + "propagate": False, + "handlers": ["console"], + } + ) +) + +logging.config.dictConfig(LOGGING_CONF) +logger = logging.getLogger('test') + + +class BaseProviderTestSuite(unittest.TestCase): + """ + Base test suite for a provider: docker, kubernetes, etc. + """ + + NULECULE_LIB_REPO = 'https://github.com/projectatomic/nulecule-library' + NULECULE_LIB_PATH = os.path.join(os.path.dirname(__file__), + 'nulecule-library') + BUILD_DIR = os.path.join(os.path.dirname(__file__), 'build') + PROVIDER = None + + @classmethod + def setUpClass(cls): + cls.fetch_nulecule_lib() + cls.build() + + @classmethod + def tearDownClass(cls): + cls.remove_image() + + def setUp(self): + self.get_initial_state() + + def tearDown(self): + self.restore_initial_state() + + def get_initial_state(self): + raise NotImplementedError + + def restore_initial_state(self): + raise NotImplementedError + + def deploy(self, app_spec, answers): + """ + Deploy app to Docker + + Args: + app_spec (str): image name or path to application + answers (dict): Answers data + + Returns: + Path of the deployed dir. + """ + destination = self.BUILD_DIR + answers_path = os.path.join(self.BUILD_DIR, 'answers.conf') + anymarkup.serialize_file(answers, + answers_path, + format='ini') + cmd = ( + 'atomic run {app_spec} -a {answers} --provider={provider} ' + '--destination={dest}').format( + app_spec=app_spec, + answers=answers_path, + provider=self.PROVIDER, + dest=destination) + subprocess.check_call(cmd, stdin=False, stderr=False, shell=True) + return destination + + def undeploy(self, app_spec, workdir): + """ + Undeploy app from Docker. + + Args: + app_spec (str): image name or path to application + workdir (str): Path to deployed application dir + """ + cmd = 'atomic stop {app_spec} {workdir}'.format( + app_spec=app_spec, workdir=workdir) + subprocess.check_output(cmd, stdin=False, stderr=False, shell=True) + + def get_tmp_answers_file(self, answers): + f = tempfile.NamedTemporaryFile(delete=False, suffix='.conf') + f.close() + anymarkup.serialize_file(answers, f.name, format='ini') + return f.name + + @property + def nulecule_lib(self): + return self.NULECULE_LIB_PATH + + @classmethod + def fetch_nulecule_lib(cls): + if not os.path.exists(cls.NULECULE_LIB_PATH): + subprocess.check_call( + 'git clone {repo} {path}'.format( + repo=cls.NULECULE_LIB_REPO, path=cls.NULECULE_LIB_PATH), + shell=True) + else: + subprocess.check_call( + 'cd nulecule-library; git checkout master; ' + 'git pull origin master', shell=True) + + @classmethod + def build(cls): + app_dir = os.path.join(cls.NULECULE_LIB_PATH, cls.APP_DIR_NAME) + + build_dir = cls.BUILD_DIR + + try: + os.rmdir(build_dir) + except: + pass + + distutils.dir_util.copy_tree(app_dir, build_dir) + + with open(os.path.join(build_dir, 'Dockerfile')) as f: + s = f.read() + + with open(os.path.join(build_dir, 'Dockerfile'), 'w') as f: + f.write(re.sub('FROM.*', 'FROM atomicapp:build', s)) + + cls.image_name = '{}-{}'.format( + cls.APP_DIR_NAME, uuid.uuid1().hex[:8]) + subprocess.check_call( + 'docker build -t {image_name} {path}'.format( + image_name=cls.image_name, path=build_dir), + stdin=False, stderr=False, + shell=True) + + @classmethod + def remove_image(cls): + subprocess.check_call('docker rmi {}'.format(cls.image_name), + stdin=False, stderr=False, + shell=True) + + +class DockerProviderTestSuite(BaseProviderTestSuite): + """ + Base test suite for Docker. + """ + PROVIDER = 'docker' + + def tearDown(self): + _containers = self._get_containers(all=True) + + for container in _containers: + if container not in self._containers: + cmd = ['docker', 'rm', '-f', container] + print cmd + subprocess.check_output(cmd) + + def assertContainerRunning(self, name, timeout=1): + start = datetime.datetime.now() + while (datetime.datetime.now() - start).total_seconds() <= timeout: + containers = self._get_containers() + for _id, container in containers.items(): + if container['names'] == name: + return True + raise AssertionError('Container: %s not running.' % name) + + def assertContainerNotRunning(self, name, timeout=1): + start = datetime.datetime.now() + while (datetime.datetime.now() - start).total_seconds() <= timeout: + container_running = False + containers = self._get_containers() + for _id, container in containers.items(): + if container.get('name') == name: + container_running = True + if container_running: + raise AssertionError('Container: %s is running' % name) + return True + + def get_initial_state(self): + self._containers = self._get_containers(all=True) + + def _get_containers(self, all=False): + cmd = ['docker', 'ps'] + if all: + cmd.append('-a') + output = subprocess.check_output(cmd) + _containers = OrderedDict() + + for line in output.splitlines()[1:]: + container = self._get_container(line) + _containers[container['id']] = container + return _containers + + def _get_container(self, line): + words = re.split(' {2,}', line) + if len(words) == 6: + words = words[:-1] + [''] + words[-1:] + container_id, image, command, created, status, ports, names = words + return { + 'id': container_id, + 'image': image, + 'command': command, + 'created': created, + 'status': status, + 'ports': ports, + 'names': names + } + + +class KubernetesProviderTestSuite(BaseProviderTestSuite): + """ + Base test suite for Kubernetes. + """ + PROVIDER = 'kubernetes' + + @classmethod + def setUpClass(cls): + super(KubernetesProviderTestSuite, cls).setUpClass() + logger.debug('setUpClass...') + logger.debug('Stopping existing kubernetes instance, if any...') + kubernetes.stop() + logger.debug('Starting kubernetes instance...') + kubernetes.start() + time.sleep(10) + cls.answers = anymarkup.parse(kubernetes.answers(), 'ini') + + @classmethod + def tearDownClass(cls): + kubernetes.stop() + + def tearDown(self): + kubernetes.clean() + kubernetes.wait() + + def assertPod(self, name, exists=True, status=None, timeout=1): + """ + Assert a kubernetes pod, if it exists, what's its status. + + We can also set a timeout to wait for the pod to + get to the desired state. + """ + start = datetime.datetime.now() + cmd = 'kubectl get pod ' + name + while (datetime.datetime.now() - start).total_seconds() <= timeout: + try: + output = subprocess.check_output(cmd, shell=True) + except subprocess.CalledProcessError: + if exists is False: + return True + continue + for line in output.splitlines()[1:]: + pod = self._get_pod_details(line) + if exists is False: + continue + result = True + if status is not None: + if pod['status'] == status: + result = result and True + else: + result = result and False + if result: + return True + if exists: + message = "Pod: %s does not exist" % name + if status is not None: + message += ' with status: %s' % status + else: + message = "Pod: %s exists." % name + raise AssertionError(message) + + def assertService(self, name, exists=True, timeout=1): + """ + Assert a kubernetes service, if it exists. + + We can also set a timeout to wait for the service to + get to the desired state. + """ + cmd = 'kubectl get service ' + name + start = datetime.datetime.now() + while (datetime.datetime.now() - start).total_seconds() <= timeout: + try: + output = subprocess.check_output(cmd, shell=True) + except subprocess.CalledProcessError: + if exists is False: + return True + continue + for line in output.splitlines()[1:]: + if exists is False: + continue + return True + if exists: + message = "Service: %s does not exist" % name + else: + message = "Service: %s exists." % name + raise AssertionError(message) + + def assertRc(self, name, exists=True, timeout=1): + """ + Assert a kubernetes rc, if it exists. + + We can also set a timeout to wait for the rc to + get to the desired state. + """ + cmd = 'kubectl get rc ' + name + start = datetime.datetime.now() + while (datetime.datetime.now() - start).total_seconds() <= timeout: + try: + output = subprocess.check_output(cmd, shell=True) + except subprocess.CalledProcessError: + if exists is False: + return True + continue + for line in output.splitlines()[1:]: + if exists is False: + continue + return True + if exists: + message = "RC: %s does not exist" % name + else: + message = "RC: %s exists." % name + raise AssertionError(message) + + def get_initial_state(self): + """Save initial state of the provider""" + self._services = self._get_services() + self._pods = self._get_pods() + self._rcs = self._get_rcs() + + def _get_services(self): + output = subprocess.check_output('kubectl get services', shell=True) + services = OrderedDict() + for line in output.splitlines()[1:]: + service = self._get_service_details(line) + services[service['name']] = service + return services + + def _get_service_details(self, line): + name, labels, selector, ips, ports = line.split() + service = { + 'name': name, + 'labels': labels, + 'selector': selector, + 'ips': ips, + 'ports': ports + } + return service + + def _get_pods(self): + output = subprocess.check_output('kubectl get pods', shell=True) + pods = OrderedDict() + for line in output.splitlines()[1:]: + pod = self._get_pod_details(line) + pods[pod['name']] = pod + return pods + + def _get_pod_details(self, line): + name, ready, status, restarts, age = line.split() + pod = { + 'name': name, + 'ready': ready, + 'status': status, + 'restarts': restarts, + 'age': age + } + return pod + + def _get_rcs(self): + output = subprocess.check_output('kubectl get rc', shell=True) + rcs = OrderedDict() + for line in output.splitlines()[1:]: + rc = self._get_rc_details(line) + rcs[rc['controller']] = rc + return rcs + + def _get_rc_details(self, line): + controller, container, image, selector, replicas = line.split() + rc = { + 'controller': controller, + 'container': container, + 'image': image, + 'selector': selector, + 'replicas': replicas + } + return rc + + +class OpenshiftProviderTestSuite(BaseProviderTestSuite): + """ + Base test suite for Openshift. + """ + PROVIDER = 'openshift' + + @classmethod + def setUpClass(cls): + super(OpenshiftProviderTestSuite, cls).setUpClass() + openshift.stop() + openshift.start() + cls.answers = anymarkup.parse(openshift.answers(), 'ini') + openshift.create_project('foo') + openshift.wait() + + @classmethod + def tearDownClass(cls): + openshift.stop() + + def setUp(self): + super(OpenshiftProviderTestSuite, self).setUp() + self.os_exec('oc project %s' % self.answers['general']['namespace']) + + def os_exec(self, cmd): + output = subprocess.check_output('docker exec -i origin %s' % cmd, shell=True) + return output + + def tearDown(self): + pods = self._get_pods() + services = self._get_services() + rcs = self._get_rcs() + + # clean up newly created pods + for pod in pods: + if pod not in self._pods: + self.os_exec('oc delete pod %s' % pod) + + # clean up newly created services + for service in services: + if service not in self._services: + self.os_exec('oc delete service %s' % service) + + # clean up newly created rcs + for rc in rcs: + if rc not in self._rcs: + self.os_exec('oc delete rc %s' % rc) + + openshift.wait() + + time.sleep(10) + + def assertPod(self, name, exists=True, status=None, timeout=1): + """ + Assert a kubernetes pod, if it exists, what's its status. + + We can also set a timeout to wait for the pod to + get to the desired state. + """ + start = datetime.datetime.now() + while (datetime.datetime.now() - start).total_seconds() <= timeout: + try: + output = self.os_exec('oc get pod %s' % name) + except subprocess.CalledProcessError: + if exists is False: + return True + continue + for line in output.splitlines()[1:]: + if exists is False: + continue + pod = self._get_pod_details(line) + result = True + if status is not None: + if pod['status'] == status: + result = result and True + else: + result = result and False + if result: + return True + if exists: + message = "Pod: %s does not exist" % name + if status is not None: + message += ' with status: %s' % status + else: + message = "Pod: %s exists." % name + raise AssertionError(message) + + def assertService(self, name, exists=True, timeout=1): + """ + Assert a kubernetes service, if it exists. + + We can also set a timeout to wait for the service to + get to the desired state. + """ + start = datetime.datetime.now() + while (datetime.datetime.now() - start).total_seconds() <= timeout: + try: + output = self.os_exec('oc get service %s' % name) + except subprocess.CalledProcessError: + if exists is False: + return True + continue + for line in output.splitlines()[1:]: + if exists is False: + continue + return True + + if exists: + message = "Service: %s does not exist" % name + else: + message = "Service: %s exists." % name + raise AssertionError(message) + + def assertRc(self, name, exists=True, timeout=1): + """ + Assert a kubernetes rc, if it exists. + + We can also set a timeout to wait for the rc to + get to the desired state. + """ + start = datetime.datetime.now() + while (datetime.datetime.now() - start).total_seconds() <= timeout: + try: + output = self.os_exec('oc get rc %s' % name) + except subprocess.CalledProcessError: + if exists is False: + return True + continue + for line in output.splitlines()[1:]: + if exists is False: + continue + return True + if exists: + message = "RC: %s does not exist" % name + else: + message = "RC: %s exists." % name + raise AssertionError(message) + + def get_initial_state(self): + """Save initial state of the provider""" + self._services = self._get_services() + self._pods = self._get_pods() + self._rcs = self._get_rcs() + + def _get_services(self): + output = self.os_exec('oc get services') + services = OrderedDict() + for line in output.splitlines()[1:]: + service = self._get_service_details(line) + services[service['name']] = service + return services + + def _get_service_details(self, line): + name, labels, selector, ips, ports = line.split() + service = { + 'name': name, + 'labels': labels, + 'selector': selector, + 'ips': ips, + 'ports': ports + } + return service + + def _get_pods(self): + output = self.os_exec('oc get pods') + pods = OrderedDict() + for line in output.splitlines()[1:]: + pod = self._get_pod_details(line) + pods[pod['name']] = pod + return pods + + def _get_pod_details(self, line): + name, ready, status, restarts, age = line.split() + pod = { + 'name': name, + 'ready': ready, + 'status': status, + 'restarts': restarts, + 'age': age + } + return pod + + def _get_rcs(self): + output = self.os_exec('oc get rc') + rcs = OrderedDict() + for line in output.splitlines()[1:]: + rc = self._get_rc_details(line) + rcs[rc['controller']] = rc + return rcs + + def _get_rc_details(self, line): + controller, container, image, selector, replicas = line.split() + rc = { + 'controller': controller, + 'container': container, + 'image': image, + 'selector': selector, + 'replicas': replicas + } + return rc diff --git a/tests/functional/providers/__init__.py b/tests/functional/providers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/functional/providers/kubernetes.py b/tests/functional/providers/kubernetes.py new file mode 100644 index 00000000..4c548c49 --- /dev/null +++ b/tests/functional/providers/kubernetes.py @@ -0,0 +1,109 @@ +import os +import subprocess +import sys +import time +import urllib + +from atomicapp.constants import K8S_VERSION + + +def start(): + if not (os.path.exists('/usr/bin/kubectl') or + os.path.exists('/usr/local/bin/kubectl')): + print "No kubectl bin exists? You can download it from %s" % ( + 'curl http://storage.googleapis.com/kubernetes-release/release/' + 'v{k8s_version}/bin/linux/amd64/kubectl'.format( + k8s_version=K8S_VERSION)) + sys.exit(1) + + cmd = ( + "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 " + "-d " + "gcr.io/google_containers/hyperkube-amd64:v{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 " + "--cluster-dns=10.0.0.10 " + "--cluster-domain=cluster.local " + "--allow-privileged=true --v=2") + ).format(k8s_version=K8S_VERSION) + + output = subprocess.check_output(cmd, shell=True) + print output + + wait_until_k8s_is_up() + + +def stop(): + cmd = """ +for run in {0..2} +do + docker ps -a | grep 'k8s_' | awk '{print $1}' | xargs --no-run-if-empty docker rm -f + docker ps -a | grep 'gcr.io/google_containers/hyperkube-amd64' | awk '{print $1}' | xargs --no-run-if-empty docker rm -f +done""" + output = subprocess.check_output(cmd, shell=True) + print output + + +def clean(): + cmd = """ +# Delete all hanging containers +echo "\n-----Cleaning / removing all pods and containers from default namespace-----\n" +kubectl get pvc,pv,svc,rc,po | grep -v 'k8s-\|NAME\|CONTROLLER\|kubernetes' | awk '{print $1}' | xargs --no-run-if-empty kubectl delete pvc,pv,svc,rc,po --grace-period=1 2>/dev/null""" + output = subprocess.check_output(cmd, shell=True) + print output + + +def answers(): + return """ +[general] +provider = kubernetes +namespace = default +""" + + +def wait(): + cmd = """ +echo "Waiting for k8s po/svc/rc to finish terminating..." +kubectl get po,svc,rc +sleep 3 # give kubectl chance to catch up to api call +while [ 1 ] +do + k8s=`kubectl get po,svc,rc | grep Terminating` + if [[ $k8s == "" ]] + then + echo "k8s po/svc/rc terminated!" + break + else + echo "..." + fi + sleep 1 +done""" + subprocess.check_call(cmd, shell=True) + + +def wait_until_k8s_is_up(): + while True: + try: + resp = urllib.urlopen('http://127.0.0.1:8080') + if resp.getcode() == 200: + break + except IOError: + pass + print '...' + time.sleep(1) + time.sleep(5) + +if __name__ == '__main__': + exec(sys.argv[1] + '()') diff --git a/tests/functional/providers/openshift.py b/tests/functional/providers/openshift.py new file mode 100644 index 00000000..691e296b --- /dev/null +++ b/tests/functional/providers/openshift.py @@ -0,0 +1,128 @@ +import base64 +import os +import subprocess +import sys +import time +import urllib2 +import ssl + +from atomicapp.constants import K8S_VERSION + + +ssl_ctx = ssl.create_default_context() +ssl_ctx.check_hostname = False +ssl_ctx.verify_mode = ssl.CERT_NONE + + +def start(): + print "Stopping existing origin container, if any..." + try: + subprocess.check_call('docker rm -f origin', shell=True) + except subprocess.CalledProcessError: + pass + if not (os.path.exists('/usr/bin/kubectl') or + os.path.exists('/usr/local/bin/kubectl')): + print "No kubectl bin exists? You can download it from %s" % ( + 'curl http://storage.googleapis.com/kubernetes-release/release/' + 'v{k8s_version}/bin/linux/amd64/kubectl'.format(k8s_version=K8S_VERSION)) + sys.exit(1) + + cmd = """ +docker run -d --name "origin" \ + --privileged --pid=host --net=host \ + -v /:/rootfs:ro -v /var/run:/var/run:rw -v /sys:/sys -v /var/lib/docker:/var/lib/docker:rw \ + -v /var/lib/origin/openshift.local.volumes:/var/lib/origin/openshift.local.volumes \ + openshift/origin start""" + output = subprocess.check_output(cmd, shell=True) + print output + wait_for_os() + + +def answers(): + req = urllib2.Request( + 'https://localhost:8443/oauth/authorize?' + 'response_type=token&client_id=openshift-challenging-client', + headers={'X-CSRF-Token': 1} + ) + base64string = base64.encodestring('openshift:openshift').replace( + '\n', '') + req.add_header('Authorization', 'Basic %s' % base64string) + f = urllib2.urlopen(req, context=ssl_ctx) + api_key = f.geturl().split('access_token=')[1].split('&')[0] + subprocess.check_call('docker exec -i origin oc config set-credentials openshift --token={api_key}'.format(api_key=api_key), shell=True) + subprocess.check_call( + 'docker exec -i origin oc config set-cluster openshift1 ' + '--server=https://localhost:8443 --insecure-skip-tls-verify=true', + shell=True) + subprocess.check_call( + 'docker exec -i origin oc config set-context openshift ' + '--cluster=openshift1 --user=openshift', shell=True) + subprocess.check_call( + 'docker exec -i origin oc config use-context openshift', shell=True) + subprocess.check_call( + 'docker exec -i origin oc config set contexts.openshift.namespace foo', + shell=True) + + time.sleep(3) + + answers = """ +[general] +provider = openshift +provider-api = https://localhost:8443 +provider-auth = {api_key} +namespace = foo +provider-tlsverify = False""".format(api_key=api_key) + print answers + return answers + + +def create_project(name): + subprocess.check_call( + 'docker exec -i origin oc new-project {}'.format(name), shell=True) + time.sleep(3) + + +def stop(): + try: + subprocess.check_output('docker rm -f origin', shell=True) + except subprocess.CalledProcessError: + return + + +def wait_for_os(): + while True: + try: + resp = urllib2.urlopen('https://127.0.0.1:8443', context=ssl_ctx) + if resp.getcode() == 200: + break + except IOError: + pass + print '...' + time.sleep(1) + time.sleep(5) + + +def wait(): + cmd = """ + echo "Waiting for oc po/svc/rc to finish terminating..." + docker exec -i origin oc get po,svc,rc + sleep 3 # give kubectl chance to catch up to api call + while [ 1 ] + do + oc=`docker exec -i origin oc get po,svc,rc | grep Terminating` + if [[ $oc == "" ]] + then + echo "oc po/svc/rc terminated!" + break + else + echo "..." + fi + sleep 1 + done""" + try: + subprocess.check_call(cmd, shell=True) + except subprocess.CalledProcessError: + pass + +if __name__ == '__main__': + exec(sys.argv[1] + '()') diff --git a/tests/functional/scripts/atomic.sh b/tests/functional/scripts/atomic.sh new file mode 100755 index 00000000..d82157ac --- /dev/null +++ b/tests/functional/scripts/atomic.sh @@ -0,0 +1,61 @@ +#!/bin/bash + +# NOTE, atomic 1.8 requires Docker 1.9 +RELEASE="1.8" +LINK="https://github.com/projectatomic/atomic/archive/v1.8.tar.gz" +SOURCE=$2 + +# Install according to github docs +# https://github.com/projectatomic/atomic/tree/master/docs/install +install_atomic() { + echo " + ########## + INSTALLING ATOMIC CLI + ########## + " + if [ "$SOURCE" == "rpm" ]; then + yum install -y atomic + return + elif [ "$SOURCE" == "master" ]; then + #clean this up later + echo "Downloading master" + git clone https://github.com/projectatomic/atomic atomic + cd atomic + else + wget $LINK -O atomic.tar.gz + tar --no-same-owner -xvf atomic.tar.gz + cd atomic-$RELEASE + fi + + # Use rhel yum, if not assume it's ubuntu / debian + if [ -f /etc/redhat-release ]; then + # Remove all previous builds of atomic (issue of upgrading 1.5 to 1.8) + rm -rf /usr/lib/python2.7/site-packages/Atomic/ /usr/lib/python2.7/site-packages/atomic-* + yum install -y epel-release python-pip pylint go-md2man + else + rm -rf /usr/local/lib/python2.7/site-packages/Atomic/ /usr/local/lib/python2.7/site-packages/atomic-* + rm -rf /usr/local/lib/python2.7/dist-packages/Atomic/ /usr/local/lib/python2.7/dist-packages/atomic-* + apt-get install -y make git python-selinux go-md2man python-pip python-dbus python-rpm pylint + ln /usr/local/bin/pylint /usr/bin/pylint + fi + + # Install all the requirements (usually done by make all) + pip install -r requirements.txt + + # Ignore any PYLINT errors and make sure you're installing via Python 2 + PYLINT=true PYTHON=/usr/bin/python2 make install + + # Remove once completed + cd .. + rm -rf atomic-$RELEASE atomic.tar.gz +} + +case "$1" in + install) + install_atomic + ;; + *) + echo $"Usage: atomic.sh {install}" + exit 1 + +esac diff --git a/tests/functional/scripts/atomicapp.sh b/tests/functional/scripts/atomicapp.sh new file mode 100755 index 00000000..30b69eed --- /dev/null +++ b/tests/functional/scripts/atomicapp.sh @@ -0,0 +1,45 @@ +#!/bin/bash +set -ex + +LINK="https://github.com/projectatomic/atomicapp" +UPSTREAM="projectatomic/atomicapp" + +install_atomicapp() { + echo " + ########## + INSTALLING ATOMICAPP CLI + + This will install atomicapp to /bin + as well as build an atomicapp container named + atomicapp:build + ########## + " + git clone $LINK + cd atomicapp + + if [ "$1" ] + then + echo "USING PR: $1" + git fetch origin pull/$1/head:PR_$1 + git checkout PR_$1 + fi + + # Install + make install + + # Build docker container + docker pull centos:7 + docker build -t atomicapp:build . + + cd .. + rm -rf atomicapp +} + +case "$1" in + install) + install_atomicapp $2 + ;; + *) + echo $"Usage: atomicpp.sh {install}" + exit 1 +esac diff --git a/tests/functional/scripts/prepare.sh b/tests/functional/scripts/prepare.sh new file mode 100755 index 00000000..315bcfe3 --- /dev/null +++ b/tests/functional/scripts/prepare.sh @@ -0,0 +1,33 @@ +#!/bin/bash + + +do_redhat() { + echo "Prepare for Red Hat" + # hyperkube causes avc denials + setenforce 0 + # make sure wget is installed + yum install -y wget + # Make sure docker is installed/started + yum install -y docker + systemctl start docker + # Make sure kubernetes client is installed/started + yum install -y kubernetes-client +} + +do_debian() { + echo "Prepare for Debian" + +} + +case "$1" in + install) + if [ -f /etc/redhat-release ]; then + do_redhat + else + do_debian + fi + ;; + *) + echo $"Usage: prepare.sh {install}" + exit 1 +esac diff --git a/tests/functional/test_docker_provider.py b/tests/functional/test_docker_provider.py new file mode 100644 index 00000000..4fbbda22 --- /dev/null +++ b/tests/functional/test_docker_provider.py @@ -0,0 +1,35 @@ +from base import DockerProviderTestSuite + + +class TestWordpress(DockerProviderTestSuite): + """ + Test Wordpress Atomic App on Kubernetes Provider + """ + APP_DIR_NAME = 'wordpress-centos7-atomicapp' + answers = { + 'general': { + 'namespace': 'default' + }, + 'mariadb-centos7-atomicapp:mariadb-atomicapp': { + 'db_user': 'foo', + 'db_pass': 'foo', + 'db_name': 'foo' + }, + 'wordpress': { + 'db_user': 'foo', + 'db_pass': 'foo', + 'db_name': 'foo' + } + } + + def test_wordpress_lifecycle(self): + app_spec = self.image_name + workdir = self.deploy(app_spec, self.answers) + + self.assertContainerRunning('wordpress-atomicapp', timeout=10) + self.assertContainerRunning('mariadb-atomicapp-app', timeout=10) + + self.undeploy(self.image_name, workdir) + + self.assertContainerNotRunning('wordpress-atomicapp', timeout=10) + self.assertContainerNotRunning('mariadb-atomicapp-app', timeout=10) diff --git a/tests/functional/test_kubernetes_provider.py b/tests/functional/test_kubernetes_provider.py new file mode 100644 index 00000000..8a1c1e03 --- /dev/null +++ b/tests/functional/test_kubernetes_provider.py @@ -0,0 +1,40 @@ +from __future__ import absolute_import + +from .base import KubernetesProviderTestSuite + + +class TestWordpress(KubernetesProviderTestSuite): + """ + Test Wordpress Atomic App on Kubernetes Provider + """ + APP_DIR_NAME = 'wordpress-centos7-atomicapp' + + def setUp(self): + super(TestWordpress, self).setUp() + self.answers.update({ + 'mariadb-centos7-atomicapp:mariadb-atomicapp': { + 'db_user': 'foo', + 'db_pass': 'foo', + 'db_name': 'foo' + }, + 'wordpress': { + 'db_user': 'foo', + 'db_pass': 'foo', + 'db_name': 'foo' + } + }) + + def test_wordpress_lifecycle(self): + app_spec = self.image_name + workdir = self.deploy(app_spec, self.answers) + + self.assertPod('wordpress', status='Running', timeout=360) + self.assertPod('mariadb', status='Running', timeout=360) + + self.assertService('wordpress', timeout=360) + self.assertService('mariadb', timeout=360) + + self.undeploy(app_spec, workdir) + + self.assertPod('wordpress', exists=False, timeout=360) + self.assertPod('mariadb', exists=False, timeout=360) diff --git a/tests/functional/test_openshift_provider.py b/tests/functional/test_openshift_provider.py new file mode 100644 index 00000000..ce912903 --- /dev/null +++ b/tests/functional/test_openshift_provider.py @@ -0,0 +1,44 @@ +from __future__ import absolute_import + +import logging + +from .base import OpenshiftProviderTestSuite + +logger = logging.getLogger() + + +class TestWordpress(OpenshiftProviderTestSuite): + """ + Test Wordpress Atomic App on Kubernetes Provider + """ + APP_DIR_NAME = 'wordpress-centos7-atomicapp' + + def setUp(self): + super(TestWordpress, self).setUp() + self.answers.update({ + 'mariadb-centos7-atomicapp:mariadb-atomicapp': { + 'db_user': 'foo', + 'db_pass': 'foo', + 'db_name': 'foo' + }, + 'wordpress': { + 'db_user': 'foo', + 'db_pass': 'foo', + 'db_name': 'foo' + } + }) + + def test_wordpress_lifecycle(self): + app_spec = self.image_name + workdir = self.deploy(app_spec, self.answers) + + self.assertPod('wordpress', status='ContainerCreating', timeout=120) + self.assertPod('mariadb', status='Running', timeout=120) + + self.assertService('wpfrontend', timeout=120) + self.assertService('mariadb', timeout=120) + + self.undeploy(app_spec, workdir) + + self.assertPod('wordpress', exists=False, timeout=120) + self.assertPod('mariadb', exists=False, timeout=120)