#!/usr/bin/python3

# This file is part of Cockpit.
#
# Copyright (C) 2016 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
import testvm
from testlib import *


non_manageable_images = [
    # No "atomic" utility
    "debian-stable",
    "debian-testing",
    "ubuntu-1804",
    "ubuntu-stable"
]

def can_manage(machine):
    return machine.image not in non_manageable_images


@skipImage("No cockpit-docker on i386", "fedora-i386")
@skipImage("No docker packaged", "rhel-8-1", "rhel-8-1-distropkg")
@skipImage("Docker not available", "fedora-31") # https://github.com/cockpit-project/cockpit/issues/12670
@skipPackage("cockpit-docker")
class TestDockerStorageDirect(MachineCase):

    def testOverview(self):
        b = self.browser
        m = self.machine

        m.execute("systemctl start docker")
        self.login_and_go("/docker")

        b.wait_text_not("#containers-storage-details .free-text", "")

        if can_manage(m):
            b.wait_present("#containers-storage-details a")



@skipImage("No cockpit-docker on i386", "fedora-i386")
@skipImage("No docker packaged", "rhel-8-1", "rhel-8-1-distropkg")
@skipImage("Docker not available", "fedora-31") # https://github.com/cockpit-project/cockpit/issues/12670
@skipPackage("cockpit-docker")
class TestDockerStorage(MachineCase):
    provision = {"machine1": {"address": "10.111.113.1/20"}}

    def setUp(self):
        # On the Atomics, we use two machines: one for running
        # cockpit-ws, and one whose docker storage pool is managed.
        # We can't do that both on a single machine since resetting
        # the pool would kill the cockpit-ws container.

        if "atomic" in testvm.DEFAULT_IMAGE:
            # We want to use a non-Atomic machine as the login machine
            # and the build machine that matches the current Atomic is suitable
            # since we can use the same set of built packages
            self.provision["login"] = {
                "address": "10.111.113.2/20",
                "image": testvm.get_build_image(testvm.DEFAULT_IMAGE)
            }

        self.allow_journal_messages('.*refusing to connect to unknown host.*')
        self.allow_journal_messages('.*host key for server is not known.*')
        MachineCase.setUp(self)

    def login(self):
        m = self.machines["machine1"]
        if "login" in self.machines:
            login_machine = self.machines["login"]
        else:
            login_machine = m
        b = self.browser

        if login_machine == m:
            self.login_and_go("/docker#/storage")
        else:
            login_machine.start_cockpit()
            b.login_and_go(None)

            # XXX - This should be simpler.
            #
            # Navigating straight to "/docker#/storage" doesn't seem
            # to work when the troubleshoot dialog shows up in the
            # middle: the hash part is lost.  First going to "/docker"
            # and then navigating to "#/storage" after adding the
            # machine causes a reload of the page, which is awkward.
            # So we first go to "/system", add the machine via the
            # dialog, and then go straight to "/docker#/storage".
            #
            # Both the loss of the hash part and the page reload look
            # like bugs.

            b.switch_to_top()
            b.go("/@10.111.113.1/system")
            b.wait_visible("#machine-troubleshoot")
            b.click('#machine-troubleshoot')
            b.wait_popup('troubleshoot-dialog')
            b.wait_text('#troubleshoot-dialog .btn-primary', "Add")
            b.click('#troubleshoot-dialog .btn-primary')
            b.wait_in_text('#troubleshoot-dialog', "Fingerprint")
            b.click('#troubleshoot-dialog .btn-primary')
            b.wait_popdown('troubleshoot-dialog')
            b.enter_page("/system", host="10.111.113.1")
            b.switch_to_top()
            b.go("/@10.111.113.1/docker#/storage")
            b.enter_page("/docker", host="10.111.113.1")
            b.wait_visible("#storage")

    def total_size(self):
        sel = "#storage-overview .used-total tr:nth-child(2) td:nth-child(2)"
        self.browser.wait_present(sel)
        return self.browser.text(sel)

    def testBasic(self):
        m = self.machines["machine1"]
        b = self.browser

        m.execute("systemctl start docker")

        self.login()

        if not can_manage(m):
            b.wait_visible("#storage-unsupported")
            return

        # should have some images
        self.assertNotEqual(m.execute("docker images -q"), "")

        # Add a disk

        # HACK - The total size should increase by 100 MiB, but it
        # doesn't always because docker-storage-setup needs to be
        # specifically told to grow the root filesystem.  So we just
        # print some numbers for now to see what it's happening.

        print("Initially", self.total_size())

        m.add_disk("100M", serial="DISK1")
        b.wait_in_text("#storage-drives", "DISK1")

        b.click("#storage-drives tr:contains(DISK1)")
        b.click("#storage-drives .btn-primary")
        b.wait_in_text(".modal-dialog:contains(Add Additional Storage)", "DISK1")
        b.click(".modal-dialog:contains(Add Additional Storage) .btn-danger")
        b.wait_not_present(".modal-dialog:contains(Add Additional Storage)")
        b.wait_not_in_text("#storage-drives", "DISK1")

        print("1 extra disk", self.total_size())

        # Add a second disk

        m.add_disk("100M", serial="DISK2")
        b.wait_in_text("#storage-drives", "DISK2")
        b.click("#storage-drives tr:contains(DISK2)")
        b.click("#storage-drives .btn-primary")
        b.wait_in_text(".modal-dialog:contains(Add Additional Storage)", "DISK2")
        b.wait_not_in_text(".modal-dialog:contains(Add Additional Storage)", "DISK1")
        b.click(".modal-dialog:contains(Add Additional Storage) .btn-danger")
        b.wait_not_present(".modal-dialog:contains(Add Additional Storage)")
        b.wait_not_in_text("#storage-drives", "DISK2")

        print("2 extra disks", self.total_size())

        # should still have images
        self.assertNotEqual(m.execute("docker images -q"), "")

    @skipImage("Can't manage docker storage here", *non_manageable_images)
    def testDevicemapper(self):
        m = self.machines["machine1"]
        b = self.browser

        # Allow docker-storage-setup to be happy with our very small disks
        m.execute("echo 'MIN_DATA_SIZE=100M\n' >> /etc/sysconfig/docker-storage-setup")

        # Switch to devicemapper driver in a dedicated volume group.
        # We use a dedicated volume group so that this also works for
        # images that don't have a volume group already.

        m.add_disk("200M", serial="DISK1")
        wait(lambda: m.execute("test -e /dev/sda && echo exists"))

        m.execute("systemctl stop docker")
        m.execute("atomic storage reset")
        m.execute("atomic storage modify --driver devicemapper --vgroup docker --add-device /dev/sda")
        m.execute("systemctl start docker")
        m.execute("docker info | grep 'Storage Driver: devicemapper'")
        print(m.execute("docker info"))

        self.login()

        print("1 disk", self.total_size())

        # Add a second disk

        m.add_disk("100M", serial="DISK2")
        b.wait_in_text("#storage-drives", "DISK2")
        b.click("#storage-drives tr:contains(DISK2)")
        b.click("#storage-drives .btn-primary")
        b.wait_in_text(".modal-dialog:contains(Add Additional Storage)", "DISK2")
        b.wait_not_in_text(".modal-dialog:contains(Add Additional Storage)", "DISK1")
        b.click(".modal-dialog:contains(Add Additional Storage) .btn-danger")
        b.wait_not_present(".modal-dialog:contains(Add Additional Storage)")
        b.wait_not_in_text("#storage-drives", "DISK2")

        print("2 disks", self.total_size())

        # Reset

        b.click("#storage-reset")
        b.click(".modal-dialog:contains(Reset Storage Pool) .btn-danger")
        b.wait_not_present(".modal-dialog:contains(Reset Storage Pool)")

        if not can_manage(m):
            b.wait_visible("#storage-unsupported")
        else:
            b.wait_in_text("#storage-drives", "DISK1")
            b.wait_in_text("#storage-drives", "DISK2")

        m.execute("docker info | grep 'Storage Driver: overlay2'")


if __name__ == '__main__':
    test_main()
