#!/usr/bin/python # -*- coding: utf-8 -*- # This file is part of Cockpit. # # Copyright (C) 2015 Red Hat, Inc. # # Cockpit is free software; you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation; either version 2.1 of the License, or # (at your option) any later version. # # Cockpit is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with Cockpit; If not, see . import parent from testlib import * import os import unittest import time import sys import re from kubelib import * # NOTE: Both TestOpenshift and TestRegistry are in this single file to # prevent them from being run concurrently. Both use a 'openshift' # machine, and we can only run a single one of those at the same time. def wait_project(machine, project): i = 0 found = True while True: try: output = machine.execute("oc get projects") if project not in output: if not found: print >> sys.stderr, output found = True raise Exception(output) break except: if i > 60: raise i = i + 1 time.sleep(2) @skipImage("Kubernetes not packaged", "debian-stable", "debian-testing", "ubuntu-1604", "ubuntu-stable", "fedora-i386") @skipImage("No cockpit-kubernetes packaged", "continuous-atomic", "fedora-atomic", "rhel-atomic") class TestOpenshift(MachineCase, OpenshiftCommonTests): additional_machines = { 'openshift': { 'machine': { 'image': 'openshift' } } } def setUp(self): super(TestOpenshift, self).setUp() self.openshift = self.machines['openshift'] self.openshift.upload(["verify/files/mock-app-openshift.json"], "/tmp") self.kubeconfig = os.path.join(self.tmpdir, "config") self.openshift.download("/root/.kube/config", self.kubeconfig) m = self.machine m.execute("mkdir -p /home/admin/.kube") m.upload([self.kubeconfig], "/home/admin/.kube/config") m.execute("chown -R admin:admin /home/admin/.kube") wait_project(self.openshift, "marmalade") # Expect the default container user limitations during testing self.openshift.execute("oc patch scc restricted -p '{ \"runAsUser\": { \"type\": \"MustRunAsRange\" } }'") self.browser.wait_timeout(120) def testConnect(self): m = self.machine b = self.browser # Make sure we can write to kubeconfig m.execute("chown -R admin:admin /home/admin/.kube") self.login_and_go("/kubernetes") b.wait_present("#service-list") b.wait_in_text("#service-list", "docker-registry") b.wait_present("a[href='#/volumes']") b.click("a[href='#/volumes']") b.wait_present(".pv-listing") b.wait_in_text(".pv-listing", "No volumes are present") b.click("a[href='#/']") b.wait_present("#kubernetes-change-connection") b.click("#kubernetes-change-connection") b.wait_present("modal-dialog") b.wait_present("#kubernetes-cluster") b.wait_present("#kubernetes-user") b.wait_not_present("#kubernetes-username") b.wait_not_present("#kubernetes-password") b.wait_not_present("#kubernetes-token") b.wait_in_text("#kubernetes-cluster button", "10-111-112-101:8443") b.wait_in_text("#kubernetes-user button", "system:admin/10-111-112-101:8443") b.wait_in_text("modal-dialog", "Client Certificate") b.click("#kubernetes-user button") b.click("#kubernetes-user ul li:last-child a") b.wait_in_text("#kubernetes-user button", "Add New User") b.wait_not_present("#kubernetes-token") b.wait_val("#kubernetes-username", "") b.wait_val("#kubernetes-password", "") b.set_val("#kubernetes-username", "new-user") b.set_val("#kubernetes-password", "new-user") b.click("modal-dialog div.modal-footer button.btn-primary") b.wait_not_present("modal-dialog") # scruffy isn't a admin b.wait_present("#service-list") b.wait_not_in_text("#service-list", "docker-registry") b.wait_present("a[href='#/volumes']") b.click("a[href='#/volumes']") b.wait_present(".pv-listing") b.wait_in_text(".pv-listing", "cannot watch all") b.click("a[href='#/']") b.wait_present("#kubernetes-change-connection") b.click("#kubernetes-change-connection") b.wait_present("modal-dialog") b.wait_present("#kubernetes-cluster") b.wait_present("#kubernetes-user") b.wait_not_present("#kubernetes-username") b.wait_not_present("#kubernetes-password") b.wait_present("#kubernetes-token") b.wait_in_text("#kubernetes-cluster button", "10-111-112-101:8443") b.wait_in_text("#kubernetes-user button", "new-user/10-111-112-101:8443") b.wait_not_in_text("modal-dialog", "Client Certificate") self.assertFalse(b.val("#kubernetes-token") == "") b.logout() # Test the saved kube config file m.execute("rm /home/admin/.kube/config") m.upload([self.kubeconfig], "/home/admin/.kube/config") m.execute("chown -R admin:admin /home/admin/.kube") self.login_and_go("/kubernetes") b.wait_present("#service-list") b.wait_in_text("#service-list", "docker-registry") @unittest.skipIf(True, "Nulecule deploys temporarily removed.") def testDeployDialog(self): b = self.browser m = self.machine b.wait_timeout(240) m.execute("systemctl start docker") # m.execute("docker pull submod/helloapache") tmpfile = os.path.join(self.tmpdir, "oc") self.openshift.download("/usr/bin/oc", tmpfile) m.upload([tmpfile], "/usr/local/bin") self.login_and_go("/kubernetes") b.wait_present("#service-list") b.wait_in_text("#service-list", "registry") # 1)check atomic version output = m.execute("atomic -v 2>&1") self.assertTrue(float(output) >= 1.1) # 2)check provider is supported m.execute("mkdir /var/tmp/invalid-app1") m.execute("""echo -e ' FROM busybox MAINTAINER cockpit LABEL io.projectatomic.nulecule.atomicappversion="0.1.11" \ RUN="docker run -it --rm \${OPT1} --privileged -v `pwd`:/atomicapp -v /run:/run -v /:/host --net=host --name \${NAME} -e NAME=\${NAME} -e IMAGE=\${IMAGE} \${IMAGE} -v \${OPT2} run \${OPT3} /atomicapp" \ STOP="docker run -it --rm \${OPT1} --privileged -v `pwd`:/atomicapp -v /run:/run -v /:/host --net=host --name \${NAME} -e NAME=\${NAME} -e IMAGE=\${IMAGE} \${IMAGE} -v \${OPT2} stop \${OPT3} /atomicapp" \ INSTALL="docker run -it --rm \${OPT1} --privileged -v `pwd`:/atomicapp -v /run:/run --name \${NAME} -e NAME=\${NAME} -e IMAGE=\${IMAGE} \${IMAGE} -v \${OPT2} install \${OPT3} --destination /atomicapp /application-entity" \ io.projectatomic.nulecule.providers="kubernetes" \ io.projectatomic.nulecule.specversion=0.0.2 \ io.projectatomic.nulecule.atomicappversion="0.1.11" ' > /var/tmp/invalid-app1/Dockerfile""") m.execute("docker build -t test/invalid-app1 /var/tmp/invalid-app1") m.execute("rm -rf /var/tmp/invalid-app1") b.click("#deploy-app") b.wait_popup("deploy-app-dialog") b.set_val("#deploy-app-type", "nulecule") b.set_val("#deploy-app-nulecule-image", "test/invalid-app1") b.set_val("#deploy-app-namespace", "mynamespace") b.click("#deploy-app-start") b.wait_not_attr("#deploy-app-start", "disabled", "disabled") b.is_visible(".modal-footer .alert") self.assertEqual(b.text(".modal-footer .alert") ,"No supported providers found.") b.dialog_cancel("#deploy-app-dialog") # 3)check atomicappversion is supported m.execute("mkdir /var/tmp/invalid-app2") m.execute("""echo -e ' FROM busybox MAINTAINER cockpit LABEL io.projectatomic.nulecule.atomicappversion="0.1.11" \ RUN="docker run -it --rm \${OPT1} --privileged -v `pwd`:/atomicapp -v /run:/run -v /:/host --net=host --name \${NAME} -e NAME=\${NAME} -e IMAGE=\${IMAGE} \${IMAGE} -v \${OPT2} run \${OPT3} /atomicapp" \ STOP="docker run -it --rm \${OPT1} --privileged -v `pwd`:/atomicapp -v /run:/run -v /:/host --net=host --name \${NAME} -e NAME=\${NAME} -e IMAGE=\${IMAGE} \${IMAGE} -v \${OPT2} stop \${OPT3} /atomicapp" \ INSTALL="docker run -it --rm \${OPT1} --privileged -v `pwd`:/atomicapp -v /run:/run --name \${NAME} -e NAME=\${NAME} -e IMAGE=\${IMAGE} \${IMAGE} -v \${OPT2} install \${OPT3} --destination /atomicapp /application-entity" \ io.projectatomic.nulecule.providers="kubernetes,openshift" \ io.projectatomic.nulecule.specversion=0.0.2 \ io.projectatomic.nulecule.atomicappversion="0.0.11" ' > /var/tmp/invalid-app2/Dockerfile""") m.execute("docker build -t test/invalid-app2 /var/tmp/invalid-app2") m.execute("rm -rf /var/tmp/invalid-app2") b.click("#deploy-app") b.wait_popup("deploy-app-dialog") b.set_val("#deploy-app-type", "nulecule") b.set_val("#deploy-app-nulecule-image", "test/invalid-app2") b.set_val("#deploy-app-namespace", "mynamespace") b.click("#deploy-app-start") b.wait_not_attr("#deploy-app-start", "disabled", "disabled") b.is_visible(".modal-footer .alert") self.assertEqual(b.text(".modal-footer .alert") ,"atomicapp version 0.0.11 is not supported.") b.dialog_cancel("#deploy-app-dialog") # 5)check for all metadata m.execute("mkdir /var/tmp/invalid-app4") m.execute("""echo -e ' FROM busybox MAINTAINER cockpit LABEL io.projectatomic.nulecule.providers="kubernetes,openshift" ' > /var/tmp/invalid-app4/Dockerfile""") m.execute("docker build -t test/invalid-app4 /var/tmp/invalid-app4") m.execute("rm -rf /var/tmp/invalid-app4") b.click("#deploy-app") b.wait_popup("deploy-app-dialog") b.set_val("#deploy-app-type", "nulecule") b.set_val("#deploy-app-nulecule-image", "test/invalid-app4") b.set_val("#deploy-app-namespace", "mynamespace") b.click("#deploy-app-start") b.wait_not_attr("#deploy-app-start", "disabled", "disabled") b.is_visible(".modal-footer .alert") self.assertEqual(b.text(".modal-footer .alert") ,"This image is not a supported Nulecule image") b.dialog_cancel("#deploy-app-dialog") # 6)check when atomicapp is not available m.execute("mkdir /var/tmp/invalid-app5") m.execute("""echo -e ' FROM busybox MAINTAINER cockpit LABEL io.projectatomic.nulecule.atomicappversion="0.1.11" \ RUN="docker run -it --rm \${OPT1} --privileged -v `pwd`:/atomicapp -v /run:/run -v /:/host --net=host --name \${NAME} -e NAME=\${NAME} -e IMAGE=\${IMAGE} \${IMAGE} -v \${OPT2} run \${OPT3} /atomicapp" \ STOP="docker run -it --rm \${OPT1} --privileged -v `pwd`:/atomicapp -v /run:/run -v /:/host --net=host --name \${NAME} -e NAME=\${NAME} -e IMAGE=\${IMAGE} \${IMAGE} -v \${OPT2} stop \${OPT3} /atomicapp" \ INSTALL="docker run -it --rm \${OPT1} --privileged -v `pwd`:/atomicapp -v /run:/run --name \${NAME} -e NAME=\${NAME} -e IMAGE=\${IMAGE} \${IMAGE} -v \${OPT2} install \${OPT3} --destination /atomicapp /application-entity" \ io.projectatomic.nulecule.providers="kubernetes,openshift" \ io.projectatomic.nulecule.specversion=0.0.2 \ io.projectatomic.nulecule.atomicappversion="0.1.11" ' > /var/tmp/invalid-app5/Dockerfile""") m.execute("docker build -t test/invalid-app5 /var/tmp/invalid-app5") m.execute("rm -rf /var/tmp/invalid-app5") b.click("#deploy-app") b.wait_popup("deploy-app-dialog") b.set_val("#deploy-app-type", "nulecule") b.set_val("#deploy-app-nulecule-image", "test/invalid-app5") b.set_val("#deploy-app-namespace", "mynamespace") b.click("#deploy-app-start") b.wait_not_attr("#deploy-app-start", "disabled", "disabled") b.is_visible(".modal-footer .alert") self.assertEqual(b.text(".modal-footer .alert") ,"Image failed to install.") b.dialog_cancel("#deploy-app-dialog") # 7) fail when Unable to pull Nulecule app b.click("#deploy-app") b.wait_popup("deploy-app-dialog") b.set_val("#deploy-app-type", "nulecule") b.set_val("#deploy-app-nulecule-image", "submod/helloapache1") b.set_val("#deploy-app-namespace", "mynamespace") b.click("#deploy-app-start") b.wait_not_attr("#deploy-app-start", "disabled", "disabled") b.is_visible(".modal-footer .alert") self.assertEqual(b.text(".modal-footer .alert") ,"Unable to pull Nulecule app image.") b.dialog_cancel("#deploy-app-dialog") # 8) check if app can be deployed b.click("#deploy-app") b.wait_popup("deploy-app-dialog") b.set_val("#deploy-app-type", "nulecule") b.set_val("#deploy-app-nulecule-image", "submod/helloapache:0.1.11") b.set_val("#deploy-app-namespace", "mynamespace") b.click("#deploy-app-start") self.allow_journal_messages('Could not find any image matching "submod/helloapache:0.1.11".') b.wait_not_attr("#deploy-app-start", "disabled", "disabled") b.click("#deploy-app-start") b.wait_popdown("deploy-app-dialog") b.click("a[href='#/list']") b.wait_present("#content .details-listing") b.wait_present(".details-listing tbody[data-id='pods/default/helloapache'] th") self.assertEqual(b.text(".details-listing tbody[data-id='pods/default/helloapache'] th"), "helloapache") def testReconnectChangeCert(self): m = self.machine b = self.browser # Try to connect with an old and non-matching client cert old_cert = ('LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUREVENDQWZXZ0F3SUJBZ0lCQnpBTkJna3Foa2' 'lHOXcwQkFRc0ZBREFtTVNRd0lnWURWUVFEREJ0dmNHVnUKYzJocFpuUXRjMmxuYm1WeVFERTBPVEEy' 'T0RFMk16RXdIaGNOTVRjd016STRNRFl4TXpVeVdoY05NVGt3TXpJNApNRFl4TXpVeldqQTNNUjR3SE' 'FZRFZRUUtFeFZ6ZVhOMFpXMDZZMngxYzNSbGNpMWhaRzFwYm5NeEZUQVRCZ05WCkJBTVRESE41YzNS' 'bGJUcGhaRzFwYmpDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUIKQU5Rel' 'J5SzUzQUhmdlZnME5GTHRSSENhMTEyK3l5a0xXdG14bjAxb2JTMy85VmovQU1UdmZwNVRkUlhKNjF' 'WcAo3L0N1L1pkQ3RDc1pyNnJpYVMxbUJGcmtTSkZJdmFjN2NNa3k2M0tVVXNaQmU5ZGFLdG1OMmhY' 'TUt0VitESStvCjJFQVJNWlV5YTZIMzJXZUpzRGM0L1lscUR5TFAxYVR3NWNwRTJPY3dWQTdoQ1dCS' 'ysyajIvZTl6RDhrYzM2R24KNG0wZWd3YWxlZ2UwTXFaUk1BbTFkenRpS3I1UWZ4MG9ZVUY3Z0JIYm' 'RjM253cGZ6a3M2K1F4Tkl6V0hlRmN2WApxcXpsMGxnT2ZGeWc0VWptYzhFcTBiKy9ER3lYSGlHNXN' 'vVmw1RGlVa1RKRjNrcURCZVVjZWNZaEx1VDd4emxzClk0bldYVFprc3lJMXVoZFJmS2NnbVZjQ0F3' 'RUFBYU0xTURNd0RnWURWUjBQQVFIL0JBUURBZ1dnTUJNR0ExVWQKSlFRTU1Bb0dDQ3NHQVFVRkJ3T' 'UNNQXdHQTFVZEV3RUIvd1FDTUFBd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQgpBRy9rTElnQ2YwK2' '1MZ1VXcWZjS2NLN0Nmdm9PbS9qL0FUSW1MR0YvSUtvQTRCWGhqMG5EcEszeVd3ZGt4d0hZCmxxUDh' 'xZ1NyQ1FaNkVoSlpMSWtjQWovTUlTUEUvSlJPa3R5TWFTMis4OGhqeGpxdUhucnZ5ODA5ZlJ5QzhF' 'R2kKeVIyRzhtNGJ5MEJrOWhENkVxbDYxb21VU0MzL2ozR3lPUGNZWDJEQjZsU2h4ZlFJVEpqUWNKQ' '0oyMnNDdlBBOApVeU9EaUgrNllZSVdtVFN5a2kzazk0Q3NOZXlRbERjNzh3a1BseUdrN0p1anFIK2p' 'KaURXSDQ2TXE3TTNaVVArCmowQWxhd3dtdllsRjBVZEIwdGRCenZWR21RbTRudEwwSkhVMGFqRnUy' 'QTYvTjJmT3VrZWI0TDR6elBzSkJFNHIKaUUyNWRJUlAvWHRoM0tjRFYyYkxtMUk9Ci0tLS0tRU5EI' 'ENFUlRJRklDQVRFLS0tLS0K') old_key = ('LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBMUROSElybmNBZC' 's5V0RRMFV1MUVjSnJYWGI3TEtRdGEyYkdmVFdodExmLzFXUDhBCnhPOStubE4xRmNuclZXbnY4Szc5' 'bDBLMEt4bXZxdUpwTFdZRVd1UklrVWk5cHp0d3lUTHJjcFJTeGtGNzExb3EKMlkzYUZjd3ExWDRNa' 'jZqWVFCRXhsVEpyb2ZmWlo0bXdOemo5aVdvUElzL1ZwUERseWtUWTV6QlVEdUVKWUVyNwphUGI5Nz' 'NNUHlSemZvYWZpYlI2REJxVjZCN1F5cGxFd0NiVjNPMklxdmxCL0hTaGhRWHVBRWR0MXplZkNsL09' 'TCnpyNURFMGpOWWQ0Vnk5ZXFyT1hTV0E1OFhLRGhTT1p6d1NyUnY3OE1iSmNlSWJteWhXWGtPSlNS' 'TWtYZVNvTUYKNVJ4NXhpRXU1UHZIT1d4amlkWmRObVN6SWpXNkYxRjhweUNaVndJREFRQUJBb0lCQ' 'UNxbjNDYlk0YWJteVBNUQpHMnlJRVhmcFNGMnAyc0QzYlYzUlhNcDhzV1hMekJBRndxdlQwTW9XME' 'xSK2tIWHRBN1NJR0tYdFhMWkZSWkMrClRwSTNyYXh2c3o2eE5wNkZUbGpEaVp6UXdBcm1ZdlNaUlg' 'vU0NnTFR0ZENRdEFtMDBUT2Z3UzNTb3R3K0xFK3AKMStoaDVtVlhFby9XNDRWeWYxNjNsRHAwOXBD' 'K3dpS0ZEa2JHVExBdnA1bnFaMnhtZDRyNzhyMi9TZmZ2YUplZQpJSlpwbENMYzMyQkVZaE4yeDRIa' 'HpqQkhOdTJwYkFXS2twUDVjWkZNS3QwSUkrRTN0UUNWMlkrYVZvNTY0TzRGCjZxMmFUUzVxMnRuaW' 'VBTS9uay8zN3hkNUVoNjRpMU8vQTU2YzdoZmxkMDVQMC9PdU9OY2dsaVVYRG44cnFvOUoKdXpFQ1F' 'ORUNnWUVBN0xOeWRQQlhkdEZRZ3NrWG56UlVYM3hhYWFCYU8xYnJYZzVmUnlMVm5DY2thWlRORXlE' 'dAp3eURxSGRUOGtRNXNIcXZ3WDNxNXR1elZBZ3NJUlhvcTZIUGtxRWdLeFZIclhTNzY1OFhLUnR1S' 'GswbE11ZkQxCnVPVVBaTTJYNVR4NXRmTmh1Zlh3dXZqTkdyN0E3ZEw4VUFiY3ZtS2VuSnF2bDNJZX' 'I0cVpkdDBDZ1lFQTVZQncKZ2pNTGJRZStEUzd1QmdsOFVmdGl5YnZCbDdTSEQ4T2RWamVOdzNEZjJ' 'uckltQVdLTVhNTk5GWldwbmhhc0g3Swp1bWtMQWdMNWFEWXJhaFJHN2ZwMGd0YnN0RVE5Uit2dFVp' 'azMxaFNHUS83dFBENU1LaU1jcFpnazhYUXI5UnlFCkVEN285bWFvUEZibnJKbFl5VXNQY0FCN3Y1W' 'FNxaVdqeFZMODI4TUNnWUVBNUhoSk1DcVVvZkZqL3ZsUFBiSnIKQmtlbmxYRGI1NDc4WEtzT3VFRW' 'RZajQ5M1ZOdHB0c1A1RnF1MytDbmNQUTAxRjR1QkZzWFMwUEtUdENMU1ZTawplZjd6WktNMUVrVUN' 'JODJuRFhSU3pKWTFoS3NwemdpUmhjaERWWTlFNEZYQlBTa1EyVWhVOW9RVXBZNGQ5dkRCCjdoVFJt' 'VXJqd2xGa3o0K3RvczdyVmxrQ2dZRUF4RW8vY0V5aVNDV29HblI2Sm5XMGZCWUxuMGxVUWlHb3B3W' 'UQKS3Z1bTUzTkNNd1p6VFByb0FIVkw1T2kzZ2ZoTWNNcHhNRkNwbHBYZXBaQTNQNnFLSS83ajZnaF' 'RPYmRueG56MgpaU0JWM21kOWt1aVdGY0dldVNlQTErMHlJOFhkMXU0RjBqTk1ZM3JZQjR1NDZQbmJ' 'ZNGNzYy9vbDNXNFNXVzZLCkRUcDJoS3NDZ1lCSlpMQys3Uy9zRGVqdkl0MTZ2Q3JzbDJlWlFsb1p0' 'clNoVCtTb0hmR1NPRXZkMXp1NStBL3UKaVVDYyt1SHdUa1c0RVpMTEdBYkUzTG1xSllJcXNkNVpUW' 'UkxWTZ1TGoyV1NGWFZYUXVLanVlTDZJdGttc1dvZgpyMHFtQU93RHdFVDFvRXlNVUJoOTJVMmhxSHR' 'aamlMdkxQdDc5aUhacDNtTnlLVjc5QXY3dVE9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=') m.execute("sed -i '/client-certificate-data:/ s/:.*$/: %s/; /client-key-data:/ s/:.*$/: %s/' /home/admin/.kube/config" % ( old_cert, old_key)) m.execute("chown -R admin:admin /home/admin/.kube") self.login_and_go("/kubernetes") b.wait_present(".curtains-ct") b.wait_visible(".curtains-ct") b.wait_in_text(".curtains-ct", "Couldn't connect to server") b.wait_in_text(".curtains-ct", "Unauthorized") b.wait_present(".curtains-ct #kubernetes-reconnect") # now provide a good certificate, and reconnect m.upload([self.kubeconfig], "/home/admin/.kube/config") m.execute("chown -R admin:admin /home/admin/.kube") b.click("#kubernetes-reconnect") b.wait_present("#service-list") @skipImage("Kubernetes not packaged", "debian-stable", "debian-testing", "ubuntu-1604", "ubuntu-stable", "fedora-i386") @skipImage("No cockpit-kubernetes packaged", "continuous-atomic", "fedora-atomic", "rhel-atomic") class TestRegistry(MachineCase): additional_machines = { 'openshift': { 'machine': { 'image': 'openshift' } } } def setUp(self): super(TestRegistry, self).setUp() self.openshift = self.machines['openshift'] # Sync over the kube config file tmpfile = os.path.join(self.tmpdir, "config") self.openshift.download("/root/.kube/config", tmpfile) with open(tmpfile, "r") as f: self.machine.execute("mkdir -p /home/admin/.kube && cat > /home/admin/.kube/config", input=f.read()) wait_project(self.openshift, "marmalade") self.browser.wait_timeout(120) def setupDockerRegistry(self): """Run a docker registry instance and populate it The OpenShift registry can pull image streams from localhost:5555 for testing. """ # set up a docker registry with cert, as openshift registry expects https self.openshift.execute("docker run -d -p 5555:5000 --name testreg " "-v /openshift.local.config/master/:/certs " "-e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/master.server.crt " "-e REGISTRY_HTTP_TLS_KEY=/certs/master.server.key " "registry:2") self.openshift.execute("while ! curl -s --connect-timeout 1 https://localhost:5555/; do sleep 1; done") self.addCleanup(self.openshift.execute, "docker rm -f testreg") # populate it with some images self.openshift.execute("docker tag registry:5000/marmalade/juggs:latest localhost:5555/juggs:latest; " "docker tag registry:5000/marmalade/juggs:2.11 localhost:5555/juggs:2.11; " "docker push localhost:5555/juggs") def testImages(self): b = self.browser o = self.openshift self.login_and_go("/kubernetes/registry") b.wait_present(".dashboard-images") # The default view should be overwhelmed with pizzazz images b.wait_in_text(".card-pf-wide.dashboard-images", "pizzazz/monster") b.wait_not_in_text(".card-pf-wide.dashboard-images", "default/busybox") b.wait_not_in_text(".card-pf-wide.dashboard-images", "marmalade/busybee") b.wait_not_in_text(".card-pf-wide.dashboard-images", "marmalade/juggs") b.wait_not_in_text(".card-pf-wide.dashboard-images", "marmalade/origin") # Filter the dashboard to marmalide project b.click(".dashboard-images .namespace-filter button") b.wait_visible(".dashboard-images .namespace-filter .dropdown-menu") b.wait_present(".dashboard-images .namespace-filter a[value='marmalade']") b.click(".dashboard-images .namespace-filter a[value='marmalade']") b.wait_not_in_text(".card-pf-wide.dashboard-images", "pizzazz/") b.wait_in_text(".card-pf-wide.dashboard-images", "marmalade/busybee") # Lets navigate to an image stream b.click("a[href='#/images/marmalade/busybee']") b.wait_in_text(".content-filter h3", "marmalade/busybee") b.click("tbody[data-id='marmalade/busybee:0.x'] tr td.listing-ct-toggle") b.wait_present("tbody[data-id='marmalade/busybee:0.x'] .listing-ct-panel dl.registry-image-tags") b.wait_in_text("tbody[data-id='marmalade/busybee:0.x'] .listing-ct-panel dl.registry-image-tags", "marmalade/busybee:0.x") # Look at the image layers b.click(".listing-ct-head li:last-child a") b.wait_present(".listing-ct-body .registry-image-layers") b.wait_visible(".listing-ct-body .registry-image-layers") b.wait_in_text(".listing-ct-body .registry-image-layers", "KiB") # Add postgres into the stream output = o.execute("oc get imagestream --namespace=marmalade --template='{{.spec}}' busybee") self.assertNotIn("postgres", output) b.click(".pficon-edit") b.wait_present("modal-dialog") b.wait_visible("#imagestream-modify-populate") b.click("#imagestream-modify-populate button") b.wait_visible("#imagestream-modify-populate .dropdown-menu") b.click("#imagestream-modify-populate a[value='pull']") b.wait_visible("#imagestream-modify-pull") b.set_val("#imagestream-modify-pull", "postgres") b.click(".btn-primary") b.wait_not_present("modal-dialog") b.wait_in_text ("#content", "postgres") output = o.execute("oc get imagestream --namespace=marmalade --template='{{.spec}}' busybee") self.assertIn("postgres", output) # Remove postgres from the stream b.click(".pficon-edit") b.wait_present("modal-dialog") b.wait_visible("#imagestream-modify-populate") b.click("#imagestream-modify-populate button") b.wait_visible("#imagestream-modify-populate .dropdown-menu") b.click("#imagestream-modify-populate a[value='none']") b.click(".btn-primary") b.wait_not_present("modal-dialog") b.wait_not_in_text ("#content", "postgres") output = o.execute("oc get imagestream --namespace=marmalade --template='{{.spec}}' busybee") self.assertNotIn("postgres", output) # Go to the images view and create a new imagestream b.click("#content a[href='#/images/marmalade']") b.wait_present("a i.pficon-add-circle-o") b.click("a i.pficon-add-circle-o") b.wait_present("modal-dialog") b.wait_visible("#imagestream-modify-name") b.set_val("#imagestream-modify-name", "zero") b.wait_val("#imagestream-modify-project-text", "marmalade") b.click("#imagestream-modify-project button") b.wait_visible("#imagestream-modify-project .dropdown-menu") b.click("#imagestream-modify-project a[value='default']") b.wait_val("#imagestream-modify-project-text", "default") b.set_val("#imagestream-modify-project-text", "###") b.click(".btn-primary") b.wait_visible(".dialog-error") b.set_val("#imagestream-modify-project-text", "default") b.click(".btn-primary") b.wait_not_present("modal-dialog") # Switch to the default namespace and look for what we created b.click("filter-bar .namespace-filter button") b.wait_visible("filter-bar .namespace-filter .dropdown-menu") b.click("filter-bar .namespace-filter a[value='default']") b.wait_visible("tbody[data-id='default/zero']") # Go to the images view and check annotations b.wait_present("tbody[data-id='default/busybox']") b.click("tbody[data-id='default/busybox'] th") b.wait_present(".content-filter h3") b.wait_in_text(".content-filter h3", "default/busybox") b.wait_in_text("#content", "Annotations") b.wait_in_text("registry-imagestream-meta", "openshift.io/image.dockerRepositoryCheck") # Delete the tagged image from its own screen b.go("#/images/marmalade/busybee:0.x") b.wait_in_text(".content-filter h3", "marmalade/busybee:0.x") b.click(".pficon-delete") b.wait_present("modal-dialog") b.click("modal-dialog .btn-danger") b.wait_not_present("modal-dialog") # Should redirect to the imagestream page b.wait_in_text(".content-filter", "Show all image streams") b.wait_not_in_text("#content", "0.x") # Delete via the main UI b.wait_present("tbody[data-id='marmalade/busybee:latest']") b.click("tbody[data-id='marmalade/busybee:latest'] tr.listing-ct-item td.listing-ct-toggle") b.wait_present("tbody[data-id='marmalade/busybee:latest'] .listing-ct-panel dl.registry-image-tags") b.wait_in_text("tbody[data-id='marmalade/busybee:latest'] .listing-ct-panel dl.registry-image-tags", "marmalade/busybee:latest") b.click("tbody[data-id='marmalade/busybee:latest'] .listing-ct-head .pficon-delete") b.wait_present("modal-dialog") b.click("modal-dialog .btn-danger") b.wait_not_present("modal-dialog") # All tags here have been removed b.wait_not_in_text("#content", "latest") # Show the image on the right screen b.go("#/images/marmalade/juggs") b.wait_in_text(".content-filter h3", "marmalade/juggs") b.wait_present("tbody[data-id='marmalade/juggs:2.9']") b.click("tbody[data-id='marmalade/juggs:2.9'] tr.listing-ct-item td.listing-ct-toggle") # Various labels should show up in this image b.wait_in_text("tbody[data-id='marmalade/juggs:2.9'] .listing-ct-panel", "Juggs Image") b.wait_in_text("tbody[data-id='marmalade/juggs:2.9'] registry-image-body", "This is a test description of an image. It can be as long as a paragraph, featuring a nice brogrammer sales pitch.") b.wait_in_text("tbody[data-id='marmalade/juggs:2.9'] registry-image-body", "http://hipsum.co") # And some key labels shouldn't show up on the metadata b.click("tbody[data-id='marmalade/juggs:2.9'] .listing-ct-head li:last-child a") b.wait_present("tbody[data-id='marmalade/juggs:2.9'] registry-image-meta") b.wait_in_text("tbody[data-id='marmalade/juggs:2.9'] registry-image-meta", "build-date=2016-03-04") def testProjectGroups(self): b = self.browser self.login_and_go("/kubernetes/registry") b.go("#/projects") b.wait_present("tbody[data-id='marmalade']") # Create a new group b.click("#add-group") b.wait_present("modal-dialog") b.wait_visible(".modal-body") b.set_val("#group_name", "production") b.click(".btn-primary") b.wait_not_present("modal-dialog") b.wait_present("tbody[data-id='marmalade']") #group page b.click("tbody[data-id='production'] tr:first-child td:nth-of-type(2)") b.wait_in_text(".content-filter h3", "production") #add member b.click("a i.pficon-add-circle-o") b.wait_present("modal-dialog") b.wait_visible("#add_user_to_group") b.click("#add_user_to_group button") b.wait_visible("#add_user_to_group .dropdown-menu") b.click(".dropdown-menu a[value='scruffy']") b.click(".btn-primary") b.wait_not_present("modal-dialog") #delete member b.click("tbody[data-id='scruffy'] tr td:last-child a i.pficon-close") b.wait_present("modal-dialog") b.wait_visible(".modal-body") b.click(".btn-primary") b.wait_not_present("modal-dialog") b.click("a i.pficon-add-circle-o") b.wait_present("modal-dialog") b.wait_visible("#add_user_to_group") b.click("#add_user_to_group button") b.wait_visible("#add_user_to_group .dropdown-menu") b.click(".dropdown-menu a[value='scruffy']") b.click(".btn-primary") b.wait_not_present("modal-dialog") #delete user b.wait_in_text(".content-filter h3", "production") b.click(".content-filter .pficon-delete") b.wait_present("modal-dialog") b.click(".btn-primary") b.wait_not_present("modal-dialog") b.wait_not_present("tbody[data-id='production']") def testProjectUsers(self): o = self.openshift b = self.browser self.login_and_go("/kubernetes/registry") b.go("#/projects") o.execute("oc get projects") o.execute("oc get rolebinding -n marmalade") b.wait_present("tbody[data-id='default']") # Create a new project b.click("#add-project") b.wait_present("modal-dialog") b.wait_visible(".modal-body") b.wait_visible("#project-new-name") b.set_val("#project-new-name", "testprojectuserproj") b.click(".btn-primary") b.wait_not_present("modal-dialog") #wait for it b.wait_present("tbody[data-id='testprojectuserproj']") o.execute("oc get projects") # Create a new user b.click("#add-user") b.wait_present("modal-dialog") b.wait_visible(".modal-body") b.wait_visible("#identities") b.set_val("#user_name", "testprojectuser") b.set_val("#identities", "anypassword:abc123") b.click(".btn-primary") b.wait_not_present("modal-dialog") #wait for it b.wait_present("tbody[data-id='testprojectuser']") #goto user page b.click("tbody[data-id='testprojectuser'] tr:first-child td:nth-of-type(2)") b.wait_in_text(".content-filter h3", "testprojectuser") #modify user b.click(".content-filter .pficon-edit") b.wait_present("modal-dialog") b.wait_visible("#identities") b.set_val("#identities", "anypassword:abc1234") b.click(".btn-primary") b.wait_not_present("modal-dialog") b.wait_in_text(".user-body dd", "anypassword:abc1234") #add project member b.click("a i.pficon-add-circle-o") b.wait_present("modal-dialog") b.wait_visible("#add_parent_for_user") b.click("#add_parent_for_user button") b.wait_visible("#add_parent_for_user .dropdown-menu") b.click(".dropdown-menu a[value='testprojectuserproj']") b.wait_visible("#add_role_for_user") b.click("#add_role_for_user button") b.wait_visible("#add_role_for_user .dropdown-menu") b.click("#add_role_for_user a[value='Admin']") b.click(".btn-primary") b.wait_not_present("modal-dialog") #delete project member X b.click("tbody[data-id='testprojectuserproj'] tr td:last-child a i.pficon-close") b.wait_present("modal-dialog") b.wait_visible(".modal-body") b.click(".btn-primary") b.wait_not_present("modal-dialog") #add project member again b.click("a i.pficon-add-circle-o") b.wait_present("modal-dialog") b.wait_visible("#add_parent_for_user") b.click("#add_parent_for_user button") b.wait_visible("#add_parent_for_user .dropdown-menu") b.click(".dropdown-menu a[value='testprojectuserproj']") b.wait_visible("#add_role_for_user") b.click("#add_role_for_user button") b.wait_visible("#add_role_for_user .dropdown-menu") b.click("#add_role_for_user a[value='Admin']") b.click(".btn-primary") b.wait_not_present("modal-dialog") #add another role to project member b.wait_present("tbody[data-id='testprojectuserproj']") b.wait_present("tbody[data-id='testprojectuserproj'] tr .btn-group") b.wait_visible("tbody[data-id='testprojectuserproj'] tr .btn-group") b.click("tbody[data-id='testprojectuserproj'] tr .btn-group button") b.wait_visible("tbody[data-id='testprojectuserproj'] tr .btn-group .dropdown-menu") b.click("tbody[data-id='testprojectuserproj'] tr .dropdown-menu a[value='Push']") b.wait_present("modal-dialog") b.wait_visible(".modal-body") b.click(".btn-primary") b.wait_not_present("modal-dialog") b.wait_present("table.listing-ct") wait(lambda: re.search(r"registry-editor\s+/registry-editor\s+testprojectuser\b", o.execute("oc get rolebinding -n testprojectuserproj"))) #delete user b.wait_in_text(".content-filter h3", "testprojectuser") b.click(".content-filter .pficon-delete") b.wait_present("modal-dialog") b.click(".btn-primary") b.wait_not_present("modal-dialog") # HACK: In order to test issue, log the output to the journal wait(lambda: not re.search(r"\btestprojectuser\b", o.execute("oc get rolebinding -n testprojectuserproj | logger -s 2>&1"))) #add/remove members for other roles b.go("#/projects/testprojectuserproj") for (role, perm) in [("Push", "editor"), ("Pull", "viewer")]: username = "testprojectuser" + role.lower() b.click("a i.pficon-add-circle-o") b.wait_present("modal-dialog") b.wait_visible("#add_member_name") b.set_val("#add_member_name", username) b.wait_visible("#add_role") b.click("#add_role button") b.wait_visible("#add_role .dropdown-menu") b.click("#add_role a[value='%s']" % role) b.click(".btn-primary") b.wait_not_present("modal-dialog") b.wait_present("tbody[data-id='%s']" % username) wait(lambda: username in o.execute("oc get rolebinding -n testprojectuserproj"), delay=5) output = o.execute("oc get rolebinding -n testprojectuserproj") self.assertRegexpMatches(output, "registry-%s\s+/registry-%s\s.*\\b%s\\b" % (perm, perm, username)) self.assertNotRegexpMatches(output, "registry-admin.*%s" % username) b.wait_present("tbody[data-id='%s']" % username) b.click("tbody[data-id='%s'] a i.pficon-close" % username) b.wait_present("modal-dialog") b.click(".btn-primary") b.wait_not_present("modal-dialog") b.wait_present("table.listing-ct") wait(lambda: username not in o.execute("oc get rolebinding -n testprojectuserproj"), delay=5) # try to add user with invalid name from testprojectuserproj page b.go("#/projects/testprojectuserproj") b.wait_present("a i.pficon-add-circle-o") b.click("a i.pficon-add-circle-o") b.wait_present("modal-dialog") b.wait_visible("#add_member_name") b.set_val("#add_member_name", "foo ^ bar") b.wait_visible("#add_role") b.click("#add_role button") b.wait_visible("#add_role .dropdown-menu") b.click("#add_role a[value='Admin']") b.click(".btn-primary") b.wait_in_text(".dialog-error", "The member name contains invalid characters.") # but email-style user name should be accepted b.set_val("#add_member_name", "foo@bar.com") b.click(".btn-primary") b.wait_not_present("modal-dialog") b.wait_present("tbody[data-id='foo@bar.com']") wait(lambda: 'foo@bar.com' in o.execute("oc get rolebinding -n testprojectuserproj"), delay=5) self.assertNotIn('foo ^ bar', o.execute("oc get rolebinding -n testprojectuserproj")) # it appears on the "All projects" page too b.go("#/projects/") b.wait_present("tbody[data-id='foo@bar.com']") # try to add user with invalid name from "All projects" page b.click("#add-user") b.wait_present("modal-dialog") b.wait_visible(".modal-body") b.wait_visible("#identities") b.set_val("#user_name", "bar ^ baz") b.set_val("#identities", "anypassword:abc123") b.click(".btn-primary") b.wait_in_text(".dialog-error", "The member name contains invalid characters.") # email-style user name should be accepted b.set_val("#user_name", "bar@baz.com") b.click(".btn-primary") b.wait_not_present("modal-dialog") b.wait_present("tbody[data-id='bar@baz.com']") def testProjectPolicy(self): o = self.openshift b = self.browser self.login_and_go("/kubernetes/registry") b.wait_present(".dashboard-images") b.go("#/projects") #wait for it b.wait_present("tbody[data-id='default']") # Create a new project b.click("#add-project") b.wait_present("modal-dialog") b.wait_visible(".modal-body") b.wait_visible("#project-new-name") b.set_val("#project-new-name", "testprojectpolicyproj") b.click(".btn-primary") b.wait_not_present("modal-dialog") #wait for it b.wait_present("tbody[data-id='testprojectpolicyproj']") o.execute("oc get projects") #goto project page b.click("tbody[data-id='testprojectpolicyproj'] tr:first-child td:nth-of-type(2)") b.wait_in_text(".content-filter h3", "testprojectpolicyproj") #add user with role b.wait_present("a i.pficon-add-circle-o") b.click("a i.pficon-add-circle-o") b.wait_present("modal-dialog") b.wait_visible("#add_member_group") b.click("#add_member_group button") b.wait_visible("#add_member_group .dropdown-menu") b.click("#add_member_group a[value='scruffy']") b.click("#add_role button") b.wait_visible("#add_role .dropdown-menu") b.click("#add_role a[value='Admin']") b.click(".btn-primary") b.wait_not_present("modal-dialog") b.wait_present(".inner-project-listing") b.wait_present("a i.pficon-add-circle-o") b.click("a i.pficon-add-circle-o") b.wait_present("modal-dialog") b.wait_visible("#add_member_group") b.click(".btn-primary") b.wait_present("modal-dialog") self.assertEqual(b.text(".dialog-error") ,"Please select a valid Member.") b.click(".btn-cancel") b.wait_not_present("modal-dialog") b.wait_present(".inner-project-listing") b.wait_present("a i.pficon-add-circle-o") b.click("a i.pficon-add-circle-o") b.wait_present("modal-dialog") b.wait_visible("#add_member_group") b.click("#add_member_group button") b.wait_visible("#add_member_group .dropdown-menu") b.click("#add_member_group a[value='scruffy']") b.click(".btn-primary") b.wait_present("modal-dialog") self.assertEqual(b.text(".dialog-error") ,"Please select a valid Role.") b.click(".btn-cancel") b.wait_not_present("modal-dialog") # Add a non-existent user b.wait_present(".inner-project-listing") b.wait_present("a i.pficon-add-circle-o") b.click("a i.pficon-add-circle-o") b.wait_present("modal-dialog") b.wait_visible("#add_member_group") b.set_val("#add_member_name", "randomuser") b.click("#add_role button") b.wait_visible("#add_role .dropdown-menu") b.click("#add_role a[value='Admin']") b.click(".btn-primary") b.wait_not_present("modal-dialog") # Add a non-existent user, negative case b.wait_present(".inner-project-listing") b.wait_present("a i.pficon-add-circle-o") b.click("a i.pficon-add-circle-o") b.wait_present("modal-dialog") b.wait_visible("#add_member_group") b.set_val("#add_member_name", "") b.click("#add_role button") b.wait_visible("#add_role .dropdown-menu") b.click("#add_role a[value='Admin']") b.click(".btn-primary") b.wait_present("modal-dialog") self.assertEqual(b.text(".dialog-error") ,"Please select a valid Member.") b.click(".btn-cancel") b.wait_not_present("modal-dialog") def testProjectAdmin(self): o = self.openshift b = self.browser m = self.machine # Log in as scruffy tmpfile = os.path.join(self.tmpdir, "scruffy") o.execute('printf "scruffy\r\nscruffy\r\n" | oc login') o.download("/root/.kube/config", tmpfile) with open(tmpfile, "r") as f: m.execute("mkdir -p /home/admin/.kube && cat > /home/admin/.kube/config", input=f.read()) self.login_and_go("/kubernetes/registry") # Make sure the default view is not visible to non cluster admins b.wait_present(".dashboard-images") b.wait_visible(".dashboard-images:nth-child(1)") b.wait_not_in_text(".card-pf-wide.dashboard-images", "default/busybox") # Show that the project displays shared access data b.wait_present("tr[data-name='marmalade']") b.wait_present("tr[data-name='marmalade'] .fa-lock") # Change the project access b.go("#/projects/marmalade") b.wait_in_text(".content-filter h3", "marmalade") b.wait_in_text(".listing-ct-body", "Project access policy only allows specific members to access images. Grant access to specific members below.") b.click(".content-filter .pficon-edit") b.wait_present("modal-dialog") b.wait_visible("#project-access-policy") b.wait_in_text("#project-access-policy button", "Private: Allow only specific users or groups to pull images") b.click("#project-access-policy button") b.wait_visible("#project-access-policy .dropdown-menu") b.click("#project-access-policy a[value='shared']") b.click(".btn-primary") b.wait_not_present("modal-dialog") b.wait_not_in_text(".listing-ct-body", "Project access policy allows all authenticated users to pull images. Grant additional access to specific members below.") output = o.execute("oc policy who-can get --namespace=marmalade imagestreams/layers") self.assertIn("system:authenticated", output) self.assertNotIn("system:unauthenticated", output) # Look for change in state b.go("#/") b.wait_present("tr[data-name='marmalade'] .fa-unlock-alt") # Change project to shared b.go("#/projects/marmalade") b.wait_in_text(".content-filter h3", "marmalade") b.click(".content-filter .pficon-edit") b.wait_present("modal-dialog") b.wait_visible("#project-access-policy") b.wait_in_text("#project-access-policy button", "Shared: Allow any authenticated user to pull images") b.click("#project-access-policy button") b.wait_visible("#project-access-policy .dropdown-menu") b.click("#project-access-policy a[value='anonymous']") b.click(".btn-primary") b.wait_not_present("modal-dialog") b.wait_in_text(".listing-ct-body", "Project access policy allows anonymous users to pull images. Grant additional push or admin access to specific members below.") output = o.execute("oc policy who-can get --namespace=marmalade imagestreams/layers") self.assertIn("system:unauthenticated", output) # Look for change in state b.go("#/") b.wait_present("tr[data-name='marmalade'] .fa-unlock") # New project doesn't exist b.go("#/") b.wait_present(".dashboard-images") output = o.execute("oc get projects") self.assertNotIn("llama", output) b.wait_not_in_text(".dashboard-images:first-child", "llama") # Create a new project b.wait_visible("a.new-project-link") b.click("a.new-project-link") b.wait_present("modal-dialog") b.wait_visible("#project-new-name") b.set_val("#project-new-name", "invalid...!") b.click(".btn-primary") b.wait_visible(".dialog-error") b.set_val("#project-new-name", "llama") b.set_val("#project-new-display", "Display llama") b.set_val("#project-new-description", "Description goes here") b.click(".btn-primary") b.wait_not_present("modal-dialog") # Check that the projcet exists b.wait_in_text(".dashboard-images:first-child", "llama") # Go and modify the project b.go("#/projects") b.wait_present("tbody[data-id='llama']") b.wait_present("tbody[data-id='llama'] tr.listing-ct-item") b.click("tbody[data-id='llama'] tr.listing-ct-item td:nth-of-type(2)") b.wait_in_text(".content-filter h3", "Display llama (llama)") b.wait_in_text("#content", "Description goes here") b.click(".pficon-edit") b.wait_present("modal-dialog") b.wait_visible("#project-new-display") b.set_val("#project-new-display", "What the llama say") b.wait_visible("#project-new-description") b.set_val("#project-new-description", "Blearrrrrrrrgh") b.click(".btn-primary") b.wait_not_present("modal-dialog") b.wait_in_text(".content-filter h3", "What the llama say (llama)") b.wait_in_text("#content", "Blearrrrrrrrgh") # Make sure it showed up in the console wait_project(o, "llama"); def testDockerCommandInfo(self): o = self.openshift b = self.browser m = self.machine # create push and pull user and login as pushuser o.execute("oadm policy add-role-to-user registry-viewer pulluser -n marmalade") o.execute("oadm policy add-role-to-user registry-editor pushuser -n marmalade") o.execute("oadm policy add-role-to-user registry-viewer pushuser -n pizzazz") tmpfile = os.path.join(self.tmpdir, "kubeconfig") o.execute('printf "pushuser\r\na\r\n" | oc login') o.download("/root/.kube/config", tmpfile) with open(tmpfile, "r") as f: m.execute("mkdir -p /home/admin/.kube && cat > /home/admin/.kube/config", input=f.read()) self.login_and_go("/kubernetes/registry") # always visible on "All projects" page b.wait_in_text("body", "Pull an image") b.wait_visible('#docker-push-commands') b.wait_visible('#docker-pull-commands') # push user should not see docker push command on pizzazz overview page (only a viewer there) b.wait_visible(".dashboard-images .namespace-filter") b.click(".dashboard-images .namespace-filter button") b.wait_visible(".dashboard-images .namespace-filter .dropdown-menu") b.wait_present(".dashboard-images .namespace-filter a[value='pizzazz']") b.click(".dashboard-images .namespace-filter a[value='pizzazz']") b.wait_visible('#docker-pull-commands') b.wait_not_visible('#docker-push-commands') # push user should see docker push and pull commands on marmalade overview page b.click(".dashboard-images .namespace-filter button") b.wait_visible(".dashboard-images .namespace-filter .dropdown-menu") b.wait_present(".dashboard-images .namespace-filter a[value='marmalade']") b.click(".dashboard-images .namespace-filter a[value='marmalade']") b.wait_visible('#docker-push-commands') # .. and also on the image page b.go("#/images/marmalade/origin") b.wait_in_text("body", "push to an image to this image stream") b.wait_in_text("body", "docker tag") b.wait_in_text("body", "docker push") b.wait_visible('.registry-imagestream-push') # log in as pulluser b.logout() o.execute('printf "pulluser\r\na\r\n" | oc login') o.download("/root/.kube/config", tmpfile) with open(tmpfile, "r") as f: m.execute("cat > /home/admin/.kube/config", input=f.read()) self.login_and_go("/kubernetes/registry") # always visible on "All projects" page b.wait_in_text("body", "Pull an image") b.wait_visible('#docker-push-commands') b.wait_visible('#docker-pull-commands') # pull user should only see docker pull command, but not push on project specific overview page b.wait_visible(".dashboard-images .namespace-filter") b.click(".dashboard-images .namespace-filter button") b.wait_visible(".dashboard-images .namespace-filter .dropdown-menu") b.wait_present(".dashboard-images .namespace-filter a[value='marmalade']") b.click(".dashboard-images .namespace-filter a[value='marmalade']") b.wait_visible('#docker-pull-commands') b.wait_not_visible('#docker-push-commands') # and neither the push command on the image page b.go("#/images/marmalade/origin") b.wait_in_text("body", "Images") b.wait_not_visible('.registry-imagestream-push') def testImagestreamImport(self): b = self.browser self.setupDockerRegistry() # Add new "alltags" image stream pulling from localhost:5555/juggs self.login_and_go("/kubernetes/registry#/images/marmalade") b.wait_present("a.pull-right span:contains('New image stream')") b.click("a.pull-right") b.wait_present("modal-dialog") b.wait_val("#imagestream-modify-project-text", "marmalade") b.set_val("#imagestream-modify-name", "alltags") b.wait_present("#imagestream-modify-populate") b.click("#imagestream-modify-populate button") b.wait_visible("#imagestream-modify-populate .dropdown-menu") b.click("#imagestream-modify-populate .dropdown-menu a[value='pull']") b.wait_present("#imagestream-modify-pull") b.wait_visible("#imagestream-modify-pull") b.set_val("#imagestream-modify-pull", "localhost:5555/juggs") b.click("modal-dialog div.modal-footer button.btn-primary") b.wait_not_present("modal-dialog") # new stream with both "latest" and "2.11" tags should now appear b.wait_present("tr.imagestream-item th:contains('marmalade/alltags')") b.wait_present('tbody[data-id="marmalade/alltags:latest"]') b.wait_present('tbody[data-id="marmalade/alltags:2.11"]') # also check with CLI output = self.openshift.execute("oc get imagestream --namespace=marmalade alltags") self.assertIn("localhost:5555/juggs", output) self.assertIn("latest", output) self.assertIn("2.11", output) # Add new "sometags" image stream pulling only the 2.11 tag b.click("a.pull-right") b.wait_present("modal-dialog") b.wait_val("#imagestream-modify-project-text", "marmalade") b.set_val("#imagestream-modify-name", "sometags") b.wait_present("#imagestream-modify-populate") b.click("#imagestream-modify-populate button") b.wait_visible("#imagestream-modify-populate .dropdown-menu") b.click("#imagestream-modify-populate .dropdown-menu a[value='tags']") b.wait_present("#imagestream-modify-tags") b.wait_visible("#imagestream-modify-tags") b.set_val("#imagestream-modify-pull", "localhost:5555/juggs") # fields.tags is not an element, type manually b.click("#imagestream-modify-tags") b.key_press(['2', '.', '1', '1']) b.click("modal-dialog div.modal-footer button.btn-primary") b.wait_not_present("modal-dialog") # new stream with only "2.11" tags should now appear b.wait_present("tr.imagestream-item th:contains('marmalade/sometags')") b.go("/kubernetes/registry#/images/marmalade/sometags") # EXFAIL: https://bugzilla.redhat.com/show_bug.cgi?id=1373332 # b.wait_present('tbody[data-id="marmalade/sometags:2.11"]') self.assertFalse(b.is_present('tbody[data-id="marmalade/sometags:latest"]')) # also check with CLI output = self.openshift.execute("oc get imagestream --namespace=marmalade sometags") self.assertIn("localhost:5555/juggs", output) self.assertIn("2.11", output) # EXFAIL: https://bugzilla.redhat.com/show_bug.cgi?id=1373332 # self.assertNotIn("latest", output) if __name__ == '__main__': test_main()