#!/usr/bin/python2
# -*- 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 <http://www.gnu.org/licenses/>.

import parent
from testlib import *

import os
import unittest
import time
import sys

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.

base_dir = os.path.dirname(os.path.realpath(__file__))


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:
                    sys.stderr.write(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", "rhel-8")
class TestOpenshift(MachineCase, OpenshiftCommonTests):
    provision = {
        "machine1": { "address": "10.111.113.1/20" },
        "openshift": { "image": "openshift", "forward": { 30000: 30000, 8443: 8443 } }
    }

    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")

        self.openshift.execute("oc project default")

        # Expect the default container user limitations during testing
        self.openshift.execute("oc patch securitycontextconstraints 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")

        # kubevirt is deployed by default, so link should appear in the menu
        b.wait_present("#vms-menu-link")

        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")

    def bootstrapKubevirt(self):
        o = self.openshift

        o.execute("/root/nested-kvm") # not enabled by default; for troubleshooting use "virt-host-validate qemu"
        o.execute("(cat /sys/module/kvm_intel/parameters/nested || cat /sys/module/kvm_amd/parameters/nested) | grep -q Y")

        o.execute("oc project kubevirt")
        o.execute("oc create serviceaccount -n kubevirt kubevirt")
        o.execute("oc adm policy add-scc-to-user privileged -n kube-system -z kubevirt")
        o.execute("oc adm policy add-scc-to-user privileged -n kube-system -z kubevirt-admin")
        o.execute("oc adm policy add-scc-to-user privileged -n kube-system -z default")

        wait(lambda: "Running" in o.execute("oc get pods | grep virt-controller"), delay=2) # is it deployed and up?
        wait(lambda: "No resources found." in o.execute("oc get vm 2>&1"), delay=2) # No VM created/started yet

    def testUndeployKubevirt(self):
        # The kubevirt is deployed by default in the openshift image.
        # Let's undeploy it and verify the "Virtual Machines" link disappears from
        # the menu (vice-versa: appearance is verified within testConnect())

        o = self.openshift
        b = self.browser

        # wait until Kubevirt fully up, otherwise deprovision fails
        o.execute("oc project kubevirt")
        wait(lambda: "Running" in o.execute("oc get pods | grep virt-controller"), delay=2)
        wait(lambda: "No resources found." in o.execute("oc get vm 2>&1"), delay=2)

        # undeploy
        # would be better to use kubevirt-ansible via kubevirt-apb, but this requires internet access (github)
        o.execute("oc project default")
        o.execute("oc login -u system:admin")
        o.execute("oc adm policy add-cluster-role-to-user cluster-admin admin")
        o.execute("oc adm policy add-cluster-role-to-user cluster-admin system:admin")
        o.execute("oc delete -f /kubevirt/kubevirt.yaml || true")
        o.execute("oc delete project kubevirt")

        wait(lambda: "virt-api" not in o.execute("oc get pods --all-namespaces"))
        wait(lambda: "virt-controller" not in o.execute("oc get pods --all-namespaces"))
        wait(lambda: "virt-handler" not in o.execute("oc get pods --all-namespaces"))

        self.login_and_go("/kubernetes")

        # kubevirt is not available (means missing in API), so link should not be present
        b.wait_not_present("#vms-menu-link")

    def testKubevirtMachinesList(self):
        self.bootstrapKubevirt()

        o = self.openshift

        # The "fedoravm" VirtualMachine is created from OfflineVirtualMachine
        o.execute("oc patch offlinevirtualmachine fedoravm --type=merge -p '{\"spec\":{\"running\": true}}'")  # let's start the VM
        wait(lambda: "fedoravm" in o.execute("oc get vm"), msg='Start of the "fedoravm" virtual machine failed.')
        wait(lambda: "virt-launcher-fedoravm" in o.execute("oc get pods"))
        wait(lambda: "Running" in o.execute("oc get pods | grep virt-launcher-fedoravm")) # Example: virt-launcher-fedoravm-----rx59t

        b = self.browser
        self.login_and_go("/kubernetes")
        # kubevirt is available, so link should be present
        b.wait_present("#vms-menu-link")
        b.click("#vms-menu-link")
        b.wait_present("tr[data-row-id='vm-fedoravm']")
        self.assertEqual(b.text("tr[data-row-id='vm-fedoravm'] th"), "fedoravm")

        # expand row and check the Overview subtab
        b.click("tr[data-row-id='vm-fedoravm']")
        b.wait_present("tr[data-row-id='vm-fedoravm'] + tr.listing-ct-panel")
        b.wait_in_text("tr[data-row-id='vm-fedoravm'] + tr.listing-ct-panel", "Node")
        b.wait_present("#vm-fedoravm-memory")
        b.wait_in_text("#vm-fedoravm-memory", "256M")
        b.wait_in_text("#vm-fedoravm-labels", "kubevirt.io/nodeName=f1.cockpit.lan")
        b.wait_present("#vm-fedoravm-pod")

        # switch to the Disks subtab
        b.wait_present("#vm-fedoravm-disks-tab")
        b.click("#vm-fedoravm-disks-tab")
        b.wait_present("#vm-fedoravm-disks-total-value") # wait for the "Count" value
        b.wait_in_text("#vm-fedoravm-disks-total-value", "2")
        b.wait_present("#vm-fedoravm-disks-disk0-device") # check disk properties
        b.wait_present("#vm-fedoravm-disks-disk1-device")
        b.wait_in_text("#vm-fedoravm-disks-disk0-device", "disk0")
        b.wait_in_text("#vm-fedoravm-disks-disk1-device", "disk1")
        b.wait_in_text("#vm-fedoravm-disks-disk0-bus", "virtio")
        b.wait_in_text("#vm-fedoravm-disks-disk1-bus", "virtio")
        b.wait_in_text("#vm-fedoravm-disks-disk0-source", "kubevirt/fedora-cloud-registry-disk-demo:latest") # registryvolume
        b.wait_in_text("#vm-fedoravm-disks-disk1-source", "cloudinitvolume")

        # TODO: add iSCSI disk to the test VM and reenable here
        # b.click("tr.listing-ct-item.listing-ct-noexpand") # test navigation to the "Volumes" page
        # b.wait_js_cond('window.location.hash === "#/volumes/iscsi-disk-alpine"')

        # return back to the virtual-machines
        b.wait_present("#vms-menu-link") # left-side menu
        b.click("#vms-menu-link")
        b.wait_present("tr[data-row-id='vm-fedoravm']") # the row is already expanded if saving of UI state works

        # delete the VM (not the OVM), the VM pod should be recreated automatically
        b.wait_present("#vm-fedoravm-delete")
        wait(lambda: "virt-launcher-fedoravm" in o.execute("oc get pods | grep Running"))
        b.click("#vm-fedoravm-delete")
        b.wait_present("#kubernetes-virtual-machines-root tr > td:contains(No virtual machines)")

        # Since the VM is created from OVM, it will be automatically restarted (spec.running: true)
        b.wait_present("tr[data-row-id='vm-fedoravm']")
        wait(lambda: "Running" in o.execute("oc get pods | grep virt-launcher-fedoravm"))

        # link to node
        b.wait_present("tr[data-row-id='vm-fedoravm']") # the just-started VM is listed
        b.click("tr[data-row-id='vm-fedoravm']")  # expand row
        b.wait_present("tr[data-row-id='vm-fedoravm'] + tr.listing-ct-panel")
        b.wait_present("#vm-fedoravm-node")
        wait(lambda: b.text("#vm-fedoravm-node > a").strip() != "-") # `-` ==  unassigned ; so wait for a change
        node_name = b.text("#vm-fedoravm-node > a").strip()
        b.click("#vm-fedoravm-node > a") # jump to "node" detail
        b.wait_js_cond('window.location.hash === "#/nodes/%s"' % (node_name,))

        # link to pod
        b.click("#vms-menu-link")
        b.wait_present("tr[data-row-id='vm-fedoravm']") # the row is already expanded if saving of UI state works
        b.wait_present("#vm-fedoravm-pod")
        wait(lambda: b.text("#vm-fedoravm-pod > a").strip() != "")
        b.wait_in_text("#vm-fedoravm-pod", "virt-launcher-fedoravm")
        pod_name = b.text("#vm-fedoravm-pod > a").strip()
        b.click("#vm-fedoravm-pod > a")
        b.wait_js_cond('window.location.hash === "#/l/pods/kubevirt/%s"' % (pod_name))

    def testKubevirtMachinesCreate(self):
        class TestCreateConfig:
            VM_CONFIG_FILE = os.path.join(base_dir, "files/mock-kube-vm.json")
            VM_CONFIG_FILE_VM_NAME = "testvm"
            INVALID_FILE = os.path.join(base_dir, "/tmp/invalid-file.json")

        class CreateVmRunner:
            def __init__(self, test_obj):
                self.browser = test_obj.browser
                self.openshift = test_obj.openshift
                self.assertEqual = test_obj.assertEqual

            def tryCreate(self, dialog):
                b = self.browser
                name = dialog.name

                dialog.open() \
                    .fill() \
                    .create()
                row_selector = "tr[data-row-id='vm-{0}']".format(name)

                b.wait_present(row_selector)
                self.assertEqual(b.text("{0} th".format(row_selector)), name)
                self.assertEqual(b.text("{0} td:nth-of-type(2)".format(row_selector)), dialog.getNamespace())

                # expand row
                b.click(row_selector)
                b.wait_present("{0} + tr.listing-ct-panel".format(row_selector))
                b.wait_in_text("{0} + tr.listing-ct-panel".format(row_selector), "Node")

                return self

            def deleteVm(self, dialog):
                b = self.browser
                self.openshift.execute("oc delete vm {0} --namespace={1}".format(dialog.name, dialog.getNamespace()))
                b.wait_not_present("tr[data-row-id='vm-{0}']".format(dialog.name))

                return self

            def checkEnvIsEmpty(self):
                b = self.browser
                b.wait_present("thead tr td")
                b.wait_in_text("thead tr td", "No virtual machines")
                b.wait(lambda: "No resources found." in self.openshift.execute("oc get vms --all-namespaces 2>&1"))

                return self

        class VmDialog:
            def __init__(self, test_obj, name, resource=None, namespace="All Projects", opts=None):
                self.browser = test_obj.browser
                self.machine = test_obj.machine
                self.snapshot = test_obj.snapshot

                self.name = name
                self.resource = resource
                self.namespace = namespace
                self.opts = opts if opts else {}

            def getNamespace(self):
                return 'default' if self.namespace == "All Projects" else self.namespace

            def open(self, preselect_namespace=True):
                b = self.browser

                if preselect_namespace:
                    self._selectFromNamespaceDropdown(self.namespace)

                b.wait_present("#create-new-vm")
                b.click("#create-new-vm")
                b.wait_present("#cockpit_modal_dialog")
                b.wait_in_text(".modal-dialog .modal-header .modal-title", "Create Virtual Machine")

                return self

            def fill(self):
                if self.opts and 'filename' in self.opts:
                    filename = self.opts['filename']
                    self._setFile("#file-input", filename)
                else:
                    self._setVal("#resource-text", self.resource)

                return self

            def cancel(self):
                b = self.browser
                if b.is_present("#cockpit_modal_dialog"):
                    b.click(".modal-footer button:contains(Cancel)")
                    b.wait_not_present("#cockpit_modal_dialog")

                return self

            def create(self):
                b = self.browser
                b.click(".modal-footer button:contains(Create)")
                b.wait_not_present("#cockpit_modal_dialog")

                return self

            def createAndExpectUiError(self, error_dict):
                b = self.browser
                b.click(".modal-footer button:contains(Create)")
                b.wait_present(".modal-footer div.alert")
                b.wait_in_text(".modal-footer div.alert", "Resolve above errors to continue")
                for key, error in error_dict.items():
                    b.wait_present(key)
                    b.wait_in_text(key, error)

                return self

            def createAndExpectBackendError(self, error):
                b = self.browser
                b.click(".modal-footer button:contains(Create)")
                b.wait_present(".modal-footer .spinner")
                b.wait_not_present(".modal-footer .spinner")
                b.wait_present(".modal-footer div.alert")
                b.wait_in_text(".modal-footer div.alert", error)

                return self

            def _selectFromNamespaceDropdown(self, value):
                b = self.browser
                dropdown_selector = "filter-project div"
                dropdown_button = "{0} button".format(dropdown_selector)
                item_selector = "{0} ul li a:contains({1})".format(dropdown_selector, value)

                b.wait_visible(dropdown_button)
                b.click(dropdown_button)

                b.wait_visible(item_selector)
                b.click(item_selector)
                b.wait_not_visible(item_selector)

            def _setFile(self, id, filename):
                b = self.browser
                b.wait_present(id)
                b.upload_file(id, filename)

            def _setVal(self, selector, value, by_key_press=False):
                b = self.browser
                value = str(value)

                b.wait_present(selector)
                b.wait_visible(selector)
                b.click(selector)
                b.focus(selector)
                b.set_val(selector, '')  # clear value
                if by_key_press:
                    b.key_press(value)
                else:
                    b.set_val(selector, value)
                b.wait_val(selector, value)

        self.bootstrapKubevirt()
        runner = CreateVmRunner(self)

        def getResource(name):
            with open(TestCreateConfig.VM_CONFIG_FILE, 'r') as f:
                return f.read().replace(TestCreateConfig.VM_CONFIG_FILE_VM_NAME, name)

        def checkDialogUiErrorTest(dialog, error_dict):
            dialog.open() \
                .fill() \
                .createAndExpectUiError(error_dict) \
                .cancel()
            runner.checkEnvIsEmpty()

        def checkDialogBackendErrorTest(dialog, error):
            dialog.open() \
                .fill() \
                .createAndExpectBackendError(error) \
                .cancel()
            runner.checkEnvIsEmpty()

        def createTest(dialog):
            runner.tryCreate(dialog) \
                .deleteVm(dialog) \
                .checkEnvIsEmpty()

        def createDuplicateTest(dialog):
            runner.tryCreate(dialog)
            dialog.open() \
                .fill() \
                .createAndExpectBackendError("already exists") \
                .cancel()
            runner.deleteVm(dialog) \
                .checkEnvIsEmpty()

        b = self.browser
        self.login_and_go("/kubernetes")

        # kubevirt is available, so link should be present
        b.wait_present("#vms-menu-link")
        b.click("#vms-menu-link")
        runner.checkEnvIsEmpty()

        checkDialogUiErrorTest(
            VmDialog(self, 'testvm1', ''),
            {'#resource-text-error': "VM definition is required"}
        )

        checkDialogUiErrorTest(
            VmDialog(self, 'testvm2', namespace='default',
                                   opts={
                                       'filename': TestCreateConfig.INVALID_FILE,
                                   }),
            {'#resource-text-error': "VM definition is required"}
        )

        checkDialogUiErrorTest(
            VmDialog(self, 'testvm3', '{a": "b"}'),
            {'#resource-text-error': "VM definition is not a valid JSON"}
        )

        checkDialogUiErrorTest(
            VmDialog(self, 'testvm4', 'abcd '),
            {'#resource-text-error': "VM definition is not a valid JSON"}
        )

        checkDialogUiErrorTest(
            VmDialog(self, 'testvm5', 4145175),
            {'#resource-text-error': "VM definition is not a valid JSON"}
        )

        checkDialogUiErrorTest(
            VmDialog(self, 'testvm6', '[{}]'),
            {'#resource-text-error': "VM definition must be an object."}
        )

        checkDialogBackendErrorTest(
            VmDialog(self, 'testvm7', '{}'),
            "Failure"
        )

        vm_name = 'testvm8'
        createTest(VmDialog(self, vm_name, getResource(vm_name)))

        vm_name = 'testvm9'
        createTest(VmDialog(self, vm_name, getResource(vm_name), namespace='default'))

        vm_name = 'testvm10'
        createTest(VmDialog(self, vm_name, getResource(vm_name), namespace='kube-public'))

        vm_name = 'testvm11'
        createTest(VmDialog(self, vm_name, getResource(vm_name), namespace='kube-system'))

        vm_name = 'testvm12'
        createDuplicateTest(VmDialog(self, vm_name, getResource(vm_name)))

        # test file input
        vm_name = TestCreateConfig.VM_CONFIG_FILE_VM_NAME
        createTest(
            VmDialog(self, vm_name, namespace='kube-public',
                                   opts={
                                       'filename': TestCreateConfig.VM_CONFIG_FILE,
                                   })
        )


class TestOpenshiftPrerelease(TestOpenshift):
    provision = {
        "machine1": { "address": "10.111.113.1/20" },
        "openshift": { "image": "openshift-prerelease", "forward": { 30000: 30000, 8443: 8443 } }
    }


@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", "rhel-8")
class TestRegistry(MachineCase, RegistryTests):
    provision = {
        "machine1": { "address": "10.111.113.1/20" },
        "openshift": { "image": "openshift" }
    }
    registry_root = "/kubernetes/registry"

    def setup_user(self, user, password):
        tmpfile = os.path.join(self.tmpdir, "kubeconfig")
        self.openshift.execute('printf "{0}\r\n{1}\r\n" | oc login'.format(user, password))
        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())

    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.openshift.execute("oc project default")

        self.browser.wait_timeout(120)


class TestRegistryPrerelease(TestRegistry):
    provision = {
        "machine1": { "address": "10.111.113.1/20" },
        "openshift": { "image": "openshift-prerelease", "forward": { 30000: 30000, 8443: 8443 } }
    }


if __name__ == '__main__':
    test_main()
